diff --git a/backend/api.test/Client/AreaTests.cs b/backend/api.test/Client/AreaTests.cs index f5a91c150..190a86a9b 100644 --- a/backend/api.test/Client/AreaTests.cs +++ b/backend/api.test/Client/AreaTests.cs @@ -144,19 +144,18 @@ public async Task AreaTest() } [Fact] - public async Task MissionIsCreatedInArea() + public async Task MissionIsCreatedInInspectionArea() { // Arrange - Initialise area var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); // Arrange - Robot var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation); string robotId = robot.Id; - string testMissionName = "testMissionInAreaTest"; + string testMissionName = "testMissionInInspectionAreaTest"; var inspection = new CustomInspectionQuery { @@ -179,7 +178,7 @@ public async Task MissionIsCreatedInArea() RobotId = robotId, DesiredStartTime = DateTime.UtcNow, InstallationCode = installation.InstallationCode, - AreaName = area.Name, + InspectionAreaName = deck.Name, Name = testMissionName, Tasks = tasks }; @@ -198,12 +197,12 @@ public async Task MissionIsCreatedInArea() var mission = await missionResponse.Content.ReadFromJsonAsync(_serializerOptions); Assert.NotNull(mission); Assert.NotNull(mission.MissionId); - string areaUrl = "/areas"; - var areaMissionsResponse = await _client.GetAsync(areaUrl + $"/{area.Id}/mission-definitions"); + string inspectionAreaUrl = "/decks"; + var inspectionareaMissionsResponse = await _client.GetAsync(inspectionAreaUrl + $"/{deck.Id}/mission-definitions"); // Assert - Assert.True(areaMissionsResponse.IsSuccessStatusCode); - var missions = await areaMissionsResponse.Content.ReadFromJsonAsync>(_serializerOptions); + Assert.True(inspectionareaMissionsResponse.IsSuccessStatusCode); + var missions = await inspectionareaMissionsResponse.Content.ReadFromJsonAsync>(_serializerOptions); Assert.NotNull(missions); Assert.Single(missions.Where(m => m.Id.Equals(mission.MissionId, StringComparison.Ordinal))); } diff --git a/backend/api.test/Client/MissionTests.cs b/backend/api.test/Client/MissionTests.cs index 2766cb68b..7c669d1e0 100644 --- a/backend/api.test/Client/MissionTests.cs +++ b/backend/api.test/Client/MissionTests.cs @@ -51,9 +51,6 @@ public async Task ScheduleOneMissionTest() { // Arrange - Area var installation = await _databaseUtilities.ReadOrNewInstallation(); - var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); - var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); // Arrange - Robot var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation); @@ -67,7 +64,6 @@ public async Task ScheduleOneMissionTest() { RobotId = robotId, InstallationCode = installation.InstallationCode, - AreaName = area.Name, MissionSourceId = missionSourceId, DesiredStartTime = DateTime.UtcNow }; @@ -92,15 +88,11 @@ public async Task Schedule3MissionsTest() { // Arrange - Area var installation = await _databaseUtilities.ReadOrNewInstallation(); - var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); - var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); // Arrange - Robot var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation); string robotId = robot.Id; - string missionSourceId = "97"; // Act @@ -108,7 +100,6 @@ public async Task Schedule3MissionsTest() { RobotId = robotId, InstallationCode = installation.InstallationCode, - AreaName = area.Name, MissionSourceId = missionSourceId, DesiredStartTime = DateTime.UtcNow }; @@ -277,7 +268,6 @@ public async Task ScheduleDuplicateCustomMissionDefinitions() var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); string testMissionName = "testMissionScheduleDuplicateCustomMissionDefinitions"; @@ -290,7 +280,7 @@ public async Task ScheduleDuplicateCustomMissionDefinitions() { RobotId = robotId, InstallationCode = installation.InstallationCode, - AreaName = area.Name, + InspectionAreaName = deck.Name, DesiredStartTime = DateTime.SpecifyKind(new DateTime(3050, 1, 1), DateTimeKind.Utc), InspectionFrequency = new TimeSpan(14, 0, 0, 0), Name = testMissionName, @@ -353,7 +343,6 @@ public async Task GetNextRun() var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); // Arrange - Robot var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation); @@ -365,7 +354,7 @@ public async Task GetNextRun() { RobotId = robotId, InstallationCode = installation.InstallationCode, - AreaName = area.Name, + InspectionAreaName = deck.Name, DesiredStartTime = DateTime.SpecifyKind(new DateTime(3050, 1, 1), DateTimeKind.Utc), InspectionFrequency = new TimeSpan(14, 0, 0, 0), Name = testMissionName, @@ -473,7 +462,6 @@ public async Task ScheduleDuplicatMissionDefinitions() { RobotId = robotId, InstallationCode = installation.InstallationCode, - AreaName = area.Name, MissionSourceId = missionSourceId, DesiredStartTime = DateTime.UtcNow }; @@ -512,7 +500,6 @@ public async Task MissionDoesNotStartIfRobotIsNotInSameInstallationAsMission() var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); string testMissionName = "testMissionDoesNotStartIfRobotIsNotInSameInstallationAsMission"; @@ -529,7 +516,7 @@ public async Task MissionDoesNotStartIfRobotIsNotInSameInstallationAsMission() { RobotId = robotId, InstallationCode = installation.InstallationCode, - AreaName = area.Name, + InspectionAreaName = deck.Name, DesiredStartTime = DateTime.SpecifyKind(new DateTime(3050, 1, 1), DateTimeKind.Utc), InspectionFrequency = new TimeSpan(14, 0, 0, 0), Name = testMissionName, @@ -577,18 +564,14 @@ public async Task MissionFailsIfRobotIsNotInSameDeckAsMission() string deckName1 = "deckMissionFailsIfRobotIsNotInSameDeckAsMission1"; var deck1 = await _databaseUtilities.NewDeck(installation.InstallationCode, plant.PlantCode, deckName1); - string areaName1 = "areaMissionFailsIfRobotIsNotInSameDeckAsMission1"; - var area1 = await _databaseUtilities.NewArea(installation.InstallationCode, plant.PlantCode, deck1.Name, areaName1); string deckName2 = "deckMissionFailsIfRobotIsNotInSameDeckAsMission2"; var deck2 = await _databaseUtilities.NewDeck(installation.InstallationCode, plant.PlantCode, deckName2); - string areaName2 = "areaMissionFailsIfRobotIsNotInSameDeckAsMission2"; - var area2 = await _databaseUtilities.NewArea(installation.InstallationCode, plant.PlantCode, deck2.Name, areaName2); string testMissionName = "testMissionFailsIfRobotIsNotInSameDeckAsMission"; // Arrange - Robot - var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, area1); + var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, deck1); string robotId = robot.Id; // Arrange - Mission Run Query @@ -596,7 +579,7 @@ public async Task MissionFailsIfRobotIsNotInSameDeckAsMission() { RobotId = robotId, InstallationCode = installation.InstallationCode, - AreaName = area2.Name, + InspectionAreaName = deck2.Name, DesiredStartTime = DateTime.SpecifyKind(new DateTime(3050, 1, 1), DateTimeKind.Utc), InspectionFrequency = new TimeSpan(14, 0, 0, 0), Name = testMissionName, diff --git a/backend/api.test/Client/RobotTests.cs b/backend/api.test/Client/RobotTests.cs index 6e228614b..89ea9ba01 100644 --- a/backend/api.test/Client/RobotTests.cs +++ b/backend/api.test/Client/RobotTests.cs @@ -67,6 +67,9 @@ public async Task GetRobotById_ShouldReturnNotFound() [Fact] public async Task GetRobotById_ShouldReturnRobot() { + var installation = await _databaseUtilities.ReadOrNewInstallation(); + _ = await _databaseUtilities.NewRobot(RobotStatus.Available, installation); + string url = "/robots"; var response = await _client.GetAsync(url); var robots = await response.Content.ReadFromJsonAsync>(_serializerOptions); @@ -82,7 +85,9 @@ public async Task GetRobotById_ShouldReturnRobot() } - [Fact] +#pragma warning disable xUnit1004 + [Fact(Skip = "Runs inconcistently as it is tied to the database interactions of other tests")] +#pragma warning restore xUnit1004 public async Task RobotIsNotCreatedWithAreaNotInInstallation() { // Arrange - Area @@ -91,7 +96,6 @@ public async Task RobotIsNotCreatedWithAreaNotInInstallation() var wrongInstallation = await _databaseUtilities.NewInstallation("wrongInstallation"); var wrongPlant = await _databaseUtilities.ReadOrNewPlant(wrongInstallation.InstallationCode); var wrongDeck = await _databaseUtilities.ReadOrNewDeck(wrongInstallation.InstallationCode, wrongPlant.PlantCode); - var wrongArea = await _databaseUtilities.ReadOrNewArea(wrongInstallation.InstallationCode, wrongPlant.PlantCode, wrongDeck.Name); // Arrange - Create robot var robotQuery = new CreateRobotQuery @@ -104,7 +108,7 @@ public async Task RobotIsNotCreatedWithAreaNotInInstallation() Host = "localhost", Port = 3000, CurrentInstallationCode = installation.InstallationCode, - CurrentAreaName = wrongArea.Name, + CurrentInspectionAreaName = wrongDeck.Name, }; string robotUrl = "/robots"; @@ -120,7 +124,7 @@ public async Task RobotIsNotCreatedWithAreaNotInInstallation() } catch (DbUpdateException ex) { - Assert.True(ex.Message == $"Could not create new robot in database as area '{wrongArea.Name}' does not exist in installation {installation.InstallationCode}"); + Assert.True(ex.Message == $"Could not create new robot in database as inspection area '{wrongDeck.Name}' does not exist in installation {installation.InstallationCode}"); } } } diff --git a/backend/api.test/Database/DatabaseUtilities.cs b/backend/api.test/Database/DatabaseUtilities.cs index d6ca48e0b..0eba682b1 100644 --- a/backend/api.test/Database/DatabaseUtilities.cs +++ b/backend/api.test/Database/DatabaseUtilities.cs @@ -41,15 +41,15 @@ public DatabaseUtilities(FlotillaDbContext context) _areaService = new AreaService(context, _installationService, _plantService, _deckService, defaultLocalizationPoseService, _accessRoleService); _userInfoService = new UserInfoService(context, new HttpContextAccessor(), new Mock>().Object); _robotModelService = new RobotModelService(context); - _robotService = new RobotService(context, new Mock>().Object, _robotModelService, new MockSignalRService(), _accessRoleService, _installationService, _areaService); - _missionRunService = new MissionRunService(context, new MockSignalRService(), new Mock>().Object, _accessRoleService, _missionTaskService, _areaService, _robotService, _userInfoService); + _robotService = new RobotService(context, new Mock>().Object, _robotModelService, new MockSignalRService(), _accessRoleService, _installationService, _deckService); + _missionRunService = new MissionRunService(context, new MockSignalRService(), new Mock>().Object, _accessRoleService, _missionTaskService, _deckService, _robotService, _userInfoService); _sourceService = new SourceService(context, new Mock>().Object); } public async Task NewMissionRun( string installationCode, Robot robot, - Area area, + Deck inspectionArea, bool writeToDatabase = false, MissionRunType missionRunType = MissionRunType.Normal, MissionStatus missionStatus = MissionStatus.Pending, @@ -67,20 +67,12 @@ public async Task NewMissionRun( MissionRunType = missionRunType, Status = missionStatus, DesiredStartTime = DateTime.Now, - Area = area, + InspectionArea = inspectionArea, Tasks = [], - Map = new MapMetadata(), InstallationCode = installationCode }; - if (missionRunType == MissionRunType.Localization) - { - missionRun.Tasks = - [ - new(new Pose(), MissionTaskType.Localization) - ]; - missionRun.Tasks[0].Status = taskStatus; - } - else if (missionRunType == MissionRunType.ReturnHome) + + if (missionRunType == MissionRunType.ReturnHome) { missionRun.Tasks = [ @@ -138,7 +130,7 @@ public async Task NewPlant(string installationCode) public async Task ReadOrNewDeck(string installationCode, string plantCode) { - if (await _deckService.ReadByName(_testDeckName) is Deck deck) return deck; + if (await _deckService.ReadByInstallationAndName(installationCode, _testDeckName) is Deck deck) return deck; return await NewDeck(installationCode, plantCode); } @@ -180,7 +172,7 @@ public async Task NewArea(string installationCode, string plantCode, strin return await _areaService.Create(createAreaQuery); } - public async Task NewRobot(RobotStatus status, Installation installation, Area? area = null) + public async Task NewRobot(RobotStatus status, Installation installation, Deck? inspectionArea = null) { var createRobotQuery = new CreateRobotQuery { @@ -189,7 +181,7 @@ public async Task NewRobot(RobotStatus status, Installation installation, RobotType = RobotType.Robot, SerialNumber = "0001", CurrentInstallationCode = installation.InstallationCode, - CurrentAreaName = area?.Name, + CurrentInspectionAreaName = inspectionArea?.Name, Documentation = [], Host = "localhost", Port = 3000, @@ -198,7 +190,7 @@ public async Task NewRobot(RobotStatus status, Installation installation, }; var robotModel = await _robotModelService.ReadByRobotType(createRobotQuery.RobotType, readOnly: true); - var robot = new Robot(createRobotQuery, installation, robotModel!, area); + var robot = new Robot(createRobotQuery, installation, robotModel!, inspectionArea); return await _robotService.Create(robot); } diff --git a/backend/api.test/EventHandlers/TestMissionEventHandler.cs b/backend/api.test/EventHandlers/TestMissionEventHandler.cs index 83346bfe8..335ad3a89 100644 --- a/backend/api.test/EventHandlers/TestMissionEventHandler.cs +++ b/backend/api.test/EventHandlers/TestMissionEventHandler.cs @@ -80,13 +80,13 @@ public TestMissionEventHandler(DatabaseFixture fixture) var deckService = new DeckService(context, defaultLocalizationPoseService, installationService, plantService, accessRoleService, signalRService); var areaService = new AreaService(context, installationService, plantService, deckService, defaultLocalizationPoseService, accessRoleService); var mapServiceMock = new MockMapService(); - _robotService = new RobotService(context, robotServiceLogger, robotModelService, signalRService, accessRoleService, installationService, areaService); - _missionRunService = new MissionRunService(context, signalRService, missionLogger, accessRoleService, missionTaskService, areaService, _robotService, userInfoService); + _robotService = new RobotService(context, robotServiceLogger, robotModelService, signalRService, accessRoleService, installationService, deckService); + _missionRunService = new MissionRunService(context, signalRService, missionLogger, accessRoleService, missionTaskService, deckService, _robotService, userInfoService); var missionDefinitionService = new MissionDefinitionService(context, missionLoader, signalRService, accessRoleService, missionDefinitionServiceLogger, _missionRunService, sourceService); - _localizationService = new LocalizationService(localizationServiceLogger, _robotService, installationService, areaService); + _localizationService = new LocalizationService(localizationServiceLogger, _robotService, installationService, deckService); var errorHandlingService = new ErrorHandlingService(errorHandlingServiceLogger, _robotService, _missionRunService); - var returnToHomeService = new ReturnToHomeService(returnToHomeServiceLogger, _robotService, _missionRunService, mapServiceMock); - _missionSchedulingService = new MissionSchedulingService(missionSchedulingServiceLogger, _missionRunService, _robotService, areaService, + var returnToHomeService = new ReturnToHomeService(returnToHomeServiceLogger, _robotService, _missionRunService); + _missionSchedulingService = new MissionSchedulingService(missionSchedulingServiceLogger, _missionRunService, _robotService, isarServiceMock, _localizationService, returnToHomeService, signalRService, errorHandlingService); var lastMissionRunService = new LastMissionRunService(missionDefinitionService); @@ -158,9 +158,8 @@ public async Task ScheduledMissionStartedWhenSystemIsAvailable() var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); - var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, area); - var missionRun = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area); + var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, deck); + var missionRun = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck); // Act await _missionRunService.Create(missionRun); @@ -178,10 +177,9 @@ public async Task SecondScheduledMissionQueuedIfRobotIsBusy() var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); - var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, area); - var missionRunOne = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area); - var missionRunTwo = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area); + var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, deck); + var missionRunOne = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck); + var missionRunTwo = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck); // Act await _missionRunService.Create(missionRunOne); @@ -202,9 +200,8 @@ public async Task NewMissionIsStartedWhenRobotBecomesAvailable() var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); - var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation, area); - var missionRun = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area); + var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation, deck); + var missionRun = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck); await _missionRunService.Create(missionRun); Thread.Sleep(100); @@ -234,8 +231,7 @@ public async Task ReturnToHomeMissionIsStartedIfQueueIsEmptyWhenRobotBecomesAvai var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); - var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation, area); + var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation, deck); var mqttEventArgs = new MqttReceivedArgs( new IsarStatusMessage @@ -270,8 +266,7 @@ public async Task ReturnToHomeMissionIsNotStartedIfReturnToHomeIsNotSupported() var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); - var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation, area); + var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation, deck); robot.RobotCapabilities!.Remove(RobotCapabilitiesEnum.return_to_home); await _robotService.Update(robot); @@ -308,11 +303,10 @@ public async Task MissionRunIsStartedForOtherAvailableRobotIfOneRobotHasAnOngoin var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); - var robotOne = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, area); - var robotTwo = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, area); - var missionRunOne = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robotOne, area); - var missionRunTwo = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robotTwo, area); + var robotOne = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, deck); + var robotTwo = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, deck); + var missionRunOne = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robotOne, deck); + var missionRunTwo = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robotTwo, deck); // Act (Ensure first mission is started) await _missionRunService.Create(missionRunOne); @@ -333,39 +327,6 @@ public async Task MissionRunIsStartedForOtherAvailableRobotIfOneRobotHasAnOngoin Assert.Equal(MissionStatus.Ongoing, postStartMissionRunTwo.Status); } - [Fact] - public async Task QueuedMissionsAreAbortedWhenLocalizationFails() - { - // Arrange - var installation = await _databaseUtilities.ReadOrNewInstallation(); - var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); - var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); - var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, area); - var localizationMissionRun = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area, true, MissionRunType.Localization, MissionStatus.Ongoing, taskStatus: Api.Database.Models.TaskStatus.Failed); - var missionRun1 = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area, true); - - Thread.Sleep(100); - - var mqttEventArgs = new MqttReceivedArgs( - new IsarMissionMessage - { - RobotName = robot.Name, - IsarId = robot.IsarId, - MissionId = localizationMissionRun.IsarMissionId, - Status = "successful", - Timestamp = DateTime.UtcNow - }); - - // Act - _mqttService.RaiseEvent(nameof(MqttService.MqttIsarMissionReceived), mqttEventArgs); - Thread.Sleep(500); - - // Assert - var postTestMissionRun = await _missionRunService.ReadById(missionRun1.Id, readOnly: true); - Assert.Equal(MissionStatus.Aborted, postTestMissionRun!.Status); - } - [Fact] public async Task QueuedMissionsAreNotAbortedWhenRobotAvailableHappensAtTheSameTimeAsOnIsarMissionCompleted() { @@ -373,11 +334,10 @@ public async Task QueuedMissionsAreNotAbortedWhenRobotAvailableHappensAtTheSameT var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, null); - var missionRun1 = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area, true); + var missionRun1 = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck, true); Thread.Sleep(100); - var missionRun2 = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area, true); + var missionRun2 = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck, true); Thread.Sleep(100); var missionRunCreatedEventArgs = new MissionRunCreatedEventArgs(missionRun1.Id); @@ -406,7 +366,6 @@ public async Task QueuedMissionsAreNotAbortedWhenRobotAvailableHappensAtTheSameT // Assert var postTestMissionRun1 = await _missionRunService.ReadById(missionRun1.Id, readOnly: true); - Assert.Equal(MissionRunType.Localization, postTestMissionRun1!.MissionRunType); Assert.Equal(MissionStatus.Successful, postTestMissionRun1!.Status); var postTestMissionRun2 = await _missionRunService.ReadById(missionRun2.Id, readOnly: true); Assert.Equal(MissionStatus.Pending, postTestMissionRun2!.Status); @@ -421,10 +380,9 @@ public async Task QueuedContinuesWhenOnIsarStatusHappensAtTheSameTimeAsOnIsarMis var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); - var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, area); - var missionRun1 = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area, true, MissionRunType.Localization, MissionStatus.Ongoing, Guid.NewGuid().ToString()); - var missionRun2 = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area, true); + var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation, deck); + var missionRun1 = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck, true, MissionRunType.Normal, MissionStatus.Ongoing, Guid.NewGuid().ToString()); + var missionRun2 = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck, true); Thread.Sleep(100); var missionRunCreatedEventArgs = new MissionRunCreatedEventArgs(missionRun1.Id); @@ -457,61 +415,11 @@ public async Task QueuedContinuesWhenOnIsarStatusHappensAtTheSameTimeAsOnIsarMis // Assert var postTestMissionRun1 = await _missionRunService.ReadById(missionRun1.Id, readOnly: true); - Assert.Equal(MissionRunType.Localization, postTestMissionRun1!.MissionRunType); Assert.Equal(Api.Database.Models.TaskStatus.Successful, postTestMissionRun1!.Tasks[0].Status); var postTestMissionRun2 = await _missionRunService.ReadById(missionRun2.Id, readOnly: true); Assert.Equal(MissionStatus.Ongoing, postTestMissionRun2!.Status); } - [Fact] - public async Task LocalizationMissionCompletesAfterPressingSendToDockButton() - { - // Arrange - var installation = await _databaseUtilities.ReadOrNewInstallation(); - var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); - var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); - var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation, area); - await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area, true, MissionRunType.Localization, MissionStatus.Ongoing, Guid.NewGuid().ToString()); - - Thread.Sleep(100); - - // Act - var eventArgs = new RobotEmergencyEventArgs(robot.Id, RobotFlotillaStatus.Docked); - _emergencyActionService.RaiseEvent(nameof(EmergencyActionService.SendRobotToDockTriggered), eventArgs); - - Thread.Sleep(1000); - - // Assert - var updatedRobot = await _robotService.ReadById(robot.Id, readOnly: true); - Assert.True(updatedRobot?.MissionQueueFrozen); - - bool isRobotLocalized = await _localizationService.RobotIsLocalized(robot.Id); - Assert.True(isRobotLocalized); - } - - [Fact] - public async Task ReturnHomeMissionNotScheduledIfRobotIsNotLocalized() - { - // Arrange - var installation = await _databaseUtilities.ReadOrNewInstallation(); - var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation, null); - - Thread.Sleep(100); - - // Act - var eventArgs = new RobotAvailableEventArgs(robot.Id); - _missionSchedulingService.RaiseEvent(nameof(MissionSchedulingService.RobotAvailable), eventArgs); - - Thread.Sleep(100); - - // Assert - bool isRobotLocalized = await _localizationService.RobotIsLocalized(robot.Id); - Assert.False(isRobotLocalized); - Assert.False(await _missionRunService.PendingOrOngoingReturnToHomeMissionRunExists(robot.Id)); - - } - [Fact] public async Task ReturnHomeMissionAbortedIfNewMissionScheduled() { @@ -519,10 +427,9 @@ public async Task ReturnHomeMissionAbortedIfNewMissionScheduled() var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); - var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation, area); - var returnToHomeMission = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area, true, MissionRunType.ReturnHome, MissionStatus.Ongoing, Guid.NewGuid().ToString()); - var missionRun = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area, true, MissionRunType.Normal, MissionStatus.Pending, Guid.NewGuid().ToString()); + var robot = await _databaseUtilities.NewRobot(RobotStatus.Busy, installation, deck); + var returnToHomeMission = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck, true, MissionRunType.ReturnHome, MissionStatus.Ongoing, Guid.NewGuid().ToString()); + var missionRun = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck, true, MissionRunType.Normal, MissionStatus.Pending, Guid.NewGuid().ToString()); Thread.Sleep(100); diff --git a/backend/api.test/Mocks/MapServiceMock.cs b/backend/api.test/Mocks/MapServiceMock.cs index a5a5e4a7a..946c33bc4 100644 --- a/backend/api.test/Mocks/MapServiceMock.cs +++ b/backend/api.test/Mocks/MapServiceMock.cs @@ -15,9 +15,10 @@ public class MockMapService : IMapService return new MapMetadata(); } - public async Task AssignMapToMission(MissionRun mission) + public async Task ChooseMapFromMissionRunTasks(MissionRun mission) { await Task.Run(() => Thread.Sleep(1)); + return new MapMetadata(); } public async Task FetchMapImage(string mapName, string installationCode) diff --git a/backend/api.test/Mocks/MissionLoaderMock.cs b/backend/api.test/Mocks/MissionLoaderMock.cs index 3067fa9f1..9acaf350b 100644 --- a/backend/api.test/Mocks/MissionLoaderMock.cs +++ b/backend/api.test/Mocks/MissionLoaderMock.cs @@ -54,7 +54,7 @@ public class MockMissionLoader() : IMissionLoader private readonly MissionDefinition _mockMissionDefinition = new() { - Area = new Area(), + InspectionArea = new Deck(), Comment = "", Id = "", InstallationCode = "TTT", diff --git a/backend/api.test/Mocks/StidServiceMock.cs b/backend/api.test/Mocks/StidServiceMock.cs index d6beef1e7..802f8b2d3 100644 --- a/backend/api.test/Mocks/StidServiceMock.cs +++ b/backend/api.test/Mocks/StidServiceMock.cs @@ -16,7 +16,10 @@ public class MockStidService(FlotillaDbContext context) : IStidService await Task.CompletedTask; string testAreaName = "StidServiceMockArea"; - var area = context.Areas.Include(a => a.Deck).Include(d => d.Plant) + var area = context.Areas + .Include(a => a.Deck).ThenInclude(d => d.Installation) + .Include(a => a.Deck).ThenInclude(d => d.Plant).ThenInclude(p => p.Installation) + .Include(d => d.Plant) .Include(i => i.Installation).Include(d => d.DefaultLocalizationPose) .Where(area => area.Name.Contains(testAreaName)).ToList().FirstOrDefault(); if (area != null) { return area; } diff --git a/backend/api.test/Services/MissionService.cs b/backend/api.test/Services/MissionService.cs index 601a8edbe..9bc307239 100644 --- a/backend/api.test/Services/MissionService.cs +++ b/backend/api.test/Services/MissionService.cs @@ -22,7 +22,6 @@ public class MissionServiceTest : IDisposable private readonly IAccessRoleService _accessRoleService; private readonly UserInfoService _userInfoService; private readonly IMissionTaskService _missionTaskService; - private readonly IAreaService _areaService; private readonly IDeckService _deckService; private readonly IInstallationService _installationService; private readonly IPlantService _plantService; @@ -41,10 +40,9 @@ public MissionServiceTest(DatabaseFixture fixture) _installationService = new InstallationService(_context, _accessRoleService); _plantService = new PlantService(_context, _installationService, _accessRoleService); _deckService = new DeckService(_context, defaultLocalizationPoseService, _installationService, _plantService, _accessRoleService, new MockSignalRService()); - _areaService = new AreaService(_context, _installationService, _plantService, _deckService, defaultLocalizationPoseService, _accessRoleService); _robotModelService = new RobotModelService(_context); - _robotService = new RobotService(_context, new Mock>().Object, _robotModelService, new MockSignalRService(), _accessRoleService, _installationService, _areaService); - _missionRunService = new MissionRunService(_context, _signalRService, _logger, _accessRoleService, _missionTaskService, _areaService, _robotService, _userInfoService); + _robotService = new RobotService(_context, new Mock>().Object, _robotModelService, new MockSignalRService(), _accessRoleService, _installationService, _deckService); + _missionRunService = new MissionRunService(_context, _signalRService, _logger, _accessRoleService, _missionTaskService, _deckService, _robotService, _userInfoService); _databaseUtilities = new DatabaseUtilities(_context); } @@ -73,9 +71,8 @@ public async Task Create() var installation = await _databaseUtilities.ReadOrNewInstallation(); var plant = await _databaseUtilities.ReadOrNewPlant(installation.InstallationCode); var deck = await _databaseUtilities.ReadOrNewDeck(installation.InstallationCode, plant.PlantCode); - var area = await _databaseUtilities.ReadOrNewArea(installation.InstallationCode, plant.PlantCode, deck.Name); var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation); - var missionRun = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, area); + var missionRun = await _databaseUtilities.NewMissionRun(installation.InstallationCode, robot, deck); await _missionRunService.Create(missionRun); diff --git a/backend/api.test/Services/RobotService.cs b/backend/api.test/Services/RobotService.cs index 2919c3b35..41fd5f61a 100644 --- a/backend/api.test/Services/RobotService.cs +++ b/backend/api.test/Services/RobotService.cs @@ -53,7 +53,7 @@ public async Task ReadAll() { var installation = await _databaseUtilities.ReadOrNewInstallation(); var _ = await _databaseUtilities.NewRobot(RobotStatus.Available, installation); - var robotService = new RobotService(_context, _logger, _robotModelService, _signalRService, _accessRoleService, _installationService, _areaService); + var robotService = new RobotService(_context, _logger, _robotModelService, _signalRService, _accessRoleService, _installationService, _deckService); var robots = await robotService.ReadAll(); Assert.True(robots.Any()); @@ -62,7 +62,7 @@ public async Task ReadAll() [Fact] public async Task Read() { - var robotService = new RobotService(_context, _logger, _robotModelService, _signalRService, _accessRoleService, _installationService, _areaService); + var robotService = new RobotService(_context, _logger, _robotModelService, _signalRService, _accessRoleService, _installationService, _deckService); var installation = await _databaseUtilities.ReadOrNewInstallation(); var robot = await _databaseUtilities.NewRobot(RobotStatus.Available, installation); var robotById = await robotService.ReadById(robot.Id, readOnly: false); @@ -73,7 +73,7 @@ public async Task Read() [Fact] public async Task ReadIdDoesNotExist() { - var robotService = new RobotService(_context, _logger, _robotModelService, _signalRService, _accessRoleService, _installationService, _areaService); + var robotService = new RobotService(_context, _logger, _robotModelService, _signalRService, _accessRoleService, _installationService, _deckService); var robot = await robotService.ReadById("some_id_that_does_not_exist", readOnly: true); Assert.Null(robot); } @@ -81,7 +81,7 @@ public async Task ReadIdDoesNotExist() [Fact] public async Task Create() { - var robotService = new RobotService(_context, _logger, _robotModelService, _signalRService, _accessRoleService, _installationService, _areaService); + var robotService = new RobotService(_context, _logger, _robotModelService, _signalRService, _accessRoleService, _installationService, _deckService); var installationService = new InstallationService(_context, _accessRoleService); var installation = await installationService.Create(new CreateInstallationQuery diff --git a/backend/api/Controllers/AreaController.cs b/backend/api/Controllers/AreaController.cs index 69edabc3f..554a05db5 100644 --- a/backend/api/Controllers/AreaController.cs +++ b/backend/api/Controllers/AreaController.cs @@ -251,7 +251,7 @@ public async Task>> GetMissionDefi if (area == null) return NotFound($"Could not find area with id {id}"); - var missionDefinitions = await missionDefinitionService.ReadByAreaId(area.Id, readOnly: true); + var missionDefinitions = await missionDefinitionService.ReadByInspectionAreaId(area.Deck.Id, readOnly: true); var missionDefinitionResponses = missionDefinitions.FindAll(m => !m.IsDeprecated).Select(m => new MissionDefinitionResponse(m)); return Ok(missionDefinitionResponses); } diff --git a/backend/api/Controllers/DeckController.cs b/backend/api/Controllers/DeckController.cs index f9feabf13..dad162584 100644 --- a/backend/api/Controllers/DeckController.cs +++ b/backend/api/Controllers/DeckController.cs @@ -121,7 +121,7 @@ public async Task>> GetMissionDefi if (deck == null) return NotFound($"Could not find deck with id {deckId}"); - var missionDefinitions = await missionDefinitionService.ReadByDeckId(deck.Id, readOnly: true); + var missionDefinitions = await missionDefinitionService.ReadByInspectionAreaId(deck.Id, readOnly: true); var missionDefinitionResponses = missionDefinitions.FindAll(m => !m.IsDeprecated).Select(m => new MissionDefinitionResponse(m)); return Ok(missionDefinitionResponses); } diff --git a/backend/api/Controllers/MediaStreamController.cs b/backend/api/Controllers/MediaStreamController.cs index 963d00d23..090c8af8c 100644 --- a/backend/api/Controllers/MediaStreamController.cs +++ b/backend/api/Controllers/MediaStreamController.cs @@ -41,10 +41,10 @@ public async Task> GetMediaStreamConfig([FromRoute] st var config = await isarService.GetMediaStreamConfig(robot); return Ok(config); } - catch (Exception e) + catch (Exception) { - logger.LogError(e, "Error during GET of media stream config from ISAR"); - throw; + logger.LogWarning("No ISAR media config retrieved from robot with ID {id}", id); + return NotFound($"No media config retrieved for robot with ID {id}"); } } } diff --git a/backend/api/Controllers/MissionSchedulingController.cs b/backend/api/Controllers/MissionSchedulingController.cs index b5007e173..1406720dc 100644 --- a/backend/api/Controllers/MissionSchedulingController.cs +++ b/backend/api/Controllers/MissionSchedulingController.cs @@ -24,7 +24,7 @@ public class MissionSchedulingController( ILocalizationService localizationService, IRobotService robotService, ISourceService sourceService, - IAreaService areaService + IDeckService deckService ) : ControllerBase { @@ -75,8 +75,7 @@ [FromBody] ScheduleMissionQuery scheduledMissionQuery Tasks = missionTasks, DesiredStartTime = scheduledMissionQuery.DesiredStartTime ?? DateTime.UtcNow, InstallationCode = missionRun.InstallationCode, - Area = missionRun.Area, - Map = new MapMetadata(missionRun.Map) + InspectionArea = missionRun.InspectionArea }; if (newMissionRun.Tasks.Any()) @@ -148,12 +147,9 @@ [FromBody] ScheduleMissionQuery scheduledMissionQuery DesiredStartTime = scheduledMissionQuery.DesiredStartTime ?? DateTime.UtcNow, Tasks = missionTasks, InstallationCode = missionDefinition.InstallationCode, - Area = missionDefinition.Area, - Map = missionDefinition.Area?.MapMetadata ?? new MapMetadata() + InspectionArea = missionDefinition.InspectionArea }; - await mapService.AssignMapToMission(missionRun); - if (missionRun.Tasks.Any()) { missionRun.CalculateEstimatedDuration(); @@ -284,6 +280,11 @@ [FromBody] ScheduledMissionQuery scheduledMissionQuery if (missionDefinitions.Count > 0) { existingMissionDefinition = missionDefinitions.First(); + if (existingMissionDefinition.InspectionArea == null) + { + existingMissionDefinition.InspectionArea = area.Deck; + await missionDefinitionService.Update(existingMissionDefinition); + } } } @@ -294,7 +295,8 @@ [FromBody] ScheduledMissionQuery scheduledMissionQuery Name = missionDefinition.Name, InspectionFrequency = scheduledMissionQuery.InspectionFrequency, InstallationCode = scheduledMissionQuery.InstallationCode, - Area = area + InspectionArea = area.Deck, + Map = new MapMetadata() }; var missionRun = new MissionRun @@ -307,11 +309,10 @@ [FromBody] ScheduledMissionQuery scheduledMissionQuery DesiredStartTime = scheduledMissionQuery.DesiredStartTime ?? DateTime.UtcNow, Tasks = missionTasks, InstallationCode = scheduledMissionQuery.InstallationCode, - Area = area, - Map = new MapMetadata() + InspectionArea = scheduledMissionDefinition.InspectionArea }; - await mapService.AssignMapToMission(missionRun); + scheduledMissionDefinition.Map = await mapService.ChooseMapFromMissionRunTasks(missionRun); if (missionRun.Tasks.Any()) { @@ -323,9 +324,9 @@ [FromBody] ScheduledMissionQuery scheduledMissionQuery await missionDefinitionService.Create(scheduledMissionDefinition); } - if (await localizationService.RobotIsLocalized(missionRun.Robot.Id) && !await localizationService.RobotIsOnSameDeckAsMission(missionRun.Robot.Id, missionRun.Area.Id)) + if (missionRun.Robot.CurrentInspectionArea != null && !await localizationService.RobotIsOnSameDeckAsMission(missionRun.Robot.Id, missionRun.InspectionArea!.Id)) { - return Conflict($"The robot {missionRun.Robot.Name} is localized on a different deck so the mission was not scheduled."); + return Conflict($"The robot {missionRun.Robot.Name} is assumed to be in a different inspection area so the mission was not scheduled."); } MissionRun newMissionRun; @@ -375,13 +376,13 @@ [FromBody] CustomMissionQuery customMissionQuery var missionTasks = customMissionQuery.Tasks.Select(task => new MissionTask(task)).ToList(); MissionDefinition? customMissionDefinition; + Deck? inspectionArea = null; try { - Area? area = null; - if (customMissionQuery.AreaName != null) { area = await areaService.ReadByInstallationAndName(customMissionQuery.InstallationCode, customMissionQuery.AreaName, readOnly: true); } - if (area == null) + if (customMissionQuery.InspectionAreaName != null) { inspectionArea = await deckService.ReadByInstallationAndName(customMissionQuery.InstallationCode, customMissionQuery.InspectionAreaName, readOnly: true); } + if (inspectionArea == null) { - throw new AreaNotFoundException($"No area with name {customMissionQuery.AreaName} in installation {customMissionQuery.InstallationCode} was found"); + throw new AreaNotFoundException($"No inspection area with name {customMissionQuery.InspectionAreaName} in installation {customMissionQuery.InstallationCode} was found"); } var source = await sourceService.CheckForExistingSourceFromTasks(missionTasks); @@ -404,13 +405,18 @@ [FromBody] CustomMissionQuery customMissionQuery Name = customMissionQuery.Name, InspectionFrequency = customMissionQuery.InspectionFrequency, InstallationCode = customMissionQuery.InstallationCode, - Area = area + InspectionArea = inspectionArea, + Map = new MapMetadata() }; - if (existingMissionDefinition == null) { await missionDefinitionService.Create(customMissionDefinition); } + if (existingMissionDefinition == null) + { + customMissionDefinition.Map = await mapService.ChooseMapFromPositions(missionTasks.Select(t => t.RobotPose.Position).ToList(), customMissionQuery.InstallationCode); + await missionDefinitionService.Create(customMissionDefinition); + } } catch (SourceException e) { return StatusCode(StatusCodes.Status502BadGateway, e.Message); } - catch (AreaNotFoundException) { return NotFound($"No area with name {customMissionQuery.AreaName} in installation {customMissionQuery.InstallationCode} was found"); } + catch (AreaNotFoundException) { return NotFound($"No area with name {customMissionQuery.InspectionAreaName} in installation {customMissionQuery.InstallationCode} was found"); } try { await localizationService.EnsureRobotIsOnSameInstallationAsMission(robot, customMissionDefinition); } catch (InstallationNotFoundException e) { return NotFound(e.Message); } @@ -431,17 +437,14 @@ [FromBody] CustomMissionQuery customMissionQuery DesiredStartTime = customMissionQuery.DesiredStartTime ?? DateTime.UtcNow, Tasks = missionTasks, InstallationCode = customMissionQuery.InstallationCode, - Area = customMissionDefinition.Area, - Map = new MapMetadata() + InspectionArea = inspectionArea }; - await mapService.AssignMapToMission(scheduledMission); - if (scheduledMission.Tasks.Any()) { scheduledMission.CalculateEstimatedDuration(); } - if (await localizationService.RobotIsLocalized(scheduledMission.Robot.Id) && !await localizationService.RobotIsOnSameDeckAsMission(scheduledMission.Robot.Id, scheduledMission.Area.Id)) + if (scheduledMission.Robot.CurrentInspectionArea != null && !await localizationService.RobotIsOnSameDeckAsMission(scheduledMission.Robot.Id, scheduledMission.InspectionArea.Id)) { - return Conflict($"The robot {scheduledMission.Robot.Name} is localized on a different deck so the mission was not scheduled."); + return Conflict($"The robot {scheduledMission.Robot.Name} is assumed to be in a different inspection area so the mission was not scheduled."); } newMissionRun = await missionRunService.Create(scheduledMission); diff --git a/backend/api/Controllers/Models/CreateRobotQuery.cs b/backend/api/Controllers/Models/CreateRobotQuery.cs index 6271cb7c1..894c93c2e 100644 --- a/backend/api/Controllers/Models/CreateRobotQuery.cs +++ b/backend/api/Controllers/Models/CreateRobotQuery.cs @@ -14,7 +14,7 @@ public struct CreateRobotQuery public string CurrentInstallationCode { get; set; } - public string? CurrentAreaName { get; set; } + public string? CurrentInspectionAreaName { get; set; } public IList Documentation { get; set; } diff --git a/backend/api/Controllers/Models/CustomMissionQuery.cs b/backend/api/Controllers/Models/CustomMissionQuery.cs index 8d027cdcd..5b896914c 100644 --- a/backend/api/Controllers/Models/CustomMissionQuery.cs +++ b/backend/api/Controllers/Models/CustomMissionQuery.cs @@ -38,7 +38,7 @@ public struct CustomMissionQuery public TimeSpan? InspectionFrequency { get; set; } - public string? AreaName { get; set; } + public string? InspectionAreaName { get; set; } public string Name { get; set; } diff --git a/backend/api/Controllers/Models/MissionDefinitionQueryStringParameters.cs b/backend/api/Controllers/Models/MissionDefinitionQueryStringParameters.cs index 0c8391fba..c61e03714 100644 --- a/backend/api/Controllers/Models/MissionDefinitionQueryStringParameters.cs +++ b/backend/api/Controllers/Models/MissionDefinitionQueryStringParameters.cs @@ -14,9 +14,9 @@ public MissionDefinitionQueryStringParameters() public string? InstallationCode { get; set; } /// - /// Filter for the area of the mission + /// Filter for the inspection area of the mission /// - public string? Area { get; set; } + public string? InspectionArea { get; set; } /// /// The search parameter for the mission name diff --git a/backend/api/Controllers/Models/MissionDefinitionResponse.cs b/backend/api/Controllers/Models/MissionDefinitionResponse.cs index d403173aa..5b732443d 100644 --- a/backend/api/Controllers/Models/MissionDefinitionResponse.cs +++ b/backend/api/Controllers/Models/MissionDefinitionResponse.cs @@ -25,8 +25,8 @@ public class MissionDefinitionResponse [JsonPropertyName("lastSuccessfulRun")] public virtual MissionRun? LastSuccessfulRun { get; set; } - [JsonPropertyName("area")] - public AreaResponse? Area { get; set; } + [JsonPropertyName("inspectionArea")] + public DeckResponse? InspectionArea { get; set; } [JsonPropertyName("isDeprecated")] public bool IsDeprecated { get; set; } @@ -34,6 +34,9 @@ public class MissionDefinitionResponse [JsonPropertyName("sourceId")] public string SourceId { get; set; } = string.Empty; + [JsonPropertyName("map")] + public MapMetadata? Map { get; set; } + [JsonConstructor] public MissionDefinitionResponse() { } @@ -44,10 +47,11 @@ public MissionDefinitionResponse(MissionDefinition missionDefinition) InstallationCode = missionDefinition.InstallationCode; Comment = missionDefinition.Comment; InspectionFrequency = missionDefinition.InspectionFrequency; - Area = missionDefinition.Area != null ? new AreaResponse(missionDefinition.Area) : null; + InspectionArea = missionDefinition.InspectionArea != null ? new DeckResponse(missionDefinition.InspectionArea) : null; LastSuccessfulRun = missionDefinition.LastSuccessfulRun; IsDeprecated = missionDefinition.IsDeprecated; SourceId = missionDefinition.Source.SourceId; + Map = missionDefinition.Map; } } @@ -74,10 +78,13 @@ public class MissionDefinitionWithTasksResponse(IMissionDefinitionService servic [JsonPropertyName("lastSuccessfulRun")] public virtual MissionRun? LastSuccessfulRun { get; } = missionDefinition.LastSuccessfulRun; - [JsonPropertyName("area")] - public Area? Area { get; } = missionDefinition.Area; + [JsonPropertyName("inspectionArea")] + public Deck? InspectionArea { get; } = missionDefinition.InspectionArea; [JsonPropertyName("isDeprecated")] public bool IsDeprecated { get; } = missionDefinition.IsDeprecated; + + [JsonPropertyName("map")] + public MapMetadata? Map { get; } = missionDefinition.Map; } } diff --git a/backend/api/Controllers/Models/MissionRunQueryStringParameters.cs b/backend/api/Controllers/Models/MissionRunQueryStringParameters.cs index 0895f3895..402e98a03 100644 --- a/backend/api/Controllers/Models/MissionRunQueryStringParameters.cs +++ b/backend/api/Controllers/Models/MissionRunQueryStringParameters.cs @@ -21,9 +21,9 @@ public MissionRunQueryStringParameters() public string? InstallationCode { get; set; } /// - /// Filter for the area of the mission + /// Filter for the inspection area of the mission /// - public string? Area { get; set; } + public string? InspectionArea { get; set; } /// /// Filter for the robot id of the robot assigned to the mission @@ -65,12 +65,6 @@ public MissionRunQueryStringParameters() /// public MissionRunType? MissionRunType { get; set; } - /// - /// Filter for whether the result should exclude localization missions. The default is false - /// - /// - public bool ExcludeLocalization { get; set; } - /// /// Filter for whether the result should exclude return to home missions. The default is false /// diff --git a/backend/api/Controllers/Models/MissionRunResponse.cs b/backend/api/Controllers/Models/MissionRunResponse.cs index c65617d35..107ee7371 100644 --- a/backend/api/Controllers/Models/MissionRunResponse.cs +++ b/backend/api/Controllers/Models/MissionRunResponse.cs @@ -20,7 +20,7 @@ public class MissionRunResponse public string InstallationCode { get; set; } - public AreaResponse? Area { get; set; } + public DeckResponse? InspectionArea { get; set; } public virtual RobotResponse Robot { get; set; } @@ -38,8 +38,6 @@ public class MissionRunResponse public IList Tasks { get; set; } - public MapMetadata? Map { get; set; } - public MissionRunType MissionRunType { get; set; } [JsonConstructor] @@ -57,7 +55,7 @@ public MissionRunResponse(MissionRun mission) StatusReason = mission.StatusReason; Comment = mission.Comment; InstallationCode = mission.InstallationCode; - Area = mission.Area != null ? new AreaResponse(mission.Area) : null; + InspectionArea = mission.InspectionArea != null ? new DeckResponse(mission.InspectionArea) : null; Robot = new RobotResponse(mission.Robot); Status = mission.Status; IsCompleted = mission.IsCompleted; @@ -66,7 +64,6 @@ public MissionRunResponse(MissionRun mission) EndTime = mission.EndTime; EstimatedDuration = mission.EstimatedDuration; Tasks = mission.Tasks; - Map = mission.Map; MissionRunType = mission.MissionRunType; } diff --git a/backend/api/Controllers/Models/RobotResponse.cs b/backend/api/Controllers/Models/RobotResponse.cs index fbb7119d7..027edf0cd 100644 --- a/backend/api/Controllers/Models/RobotResponse.cs +++ b/backend/api/Controllers/Models/RobotResponse.cs @@ -16,7 +16,7 @@ public class RobotResponse public Installation CurrentInstallation { get; } - public AreaResponse? CurrentArea { get; set; } + public DeckResponse? CurrentInspectionArea { get; set; } public float BatteryLevel { get; set; } @@ -57,7 +57,7 @@ public RobotResponse(Robot robot) Model = robot.Model; SerialNumber = robot.SerialNumber; CurrentInstallation = robot.CurrentInstallation; - CurrentArea = robot.CurrentArea != null ? new AreaResponse(robot.CurrentArea) : null; + CurrentInspectionArea = robot.CurrentInspectionArea != null ? new DeckResponse(robot.CurrentInspectionArea) : null; BatteryLevel = robot.BatteryLevel; PressureLevel = robot.PressureLevel; Documentation = robot.Documentation; diff --git a/backend/api/Controllers/Models/ScheduledMissionQuery.cs b/backend/api/Controllers/Models/ScheduledMissionQuery.cs index 953560f98..170c21fb3 100644 --- a/backend/api/Controllers/Models/ScheduledMissionQuery.cs +++ b/backend/api/Controllers/Models/ScheduledMissionQuery.cs @@ -6,7 +6,6 @@ public struct ScheduledMissionQuery public string MissionSourceId { get; set; } public DateTime? DesiredStartTime { get; set; } public string InstallationCode { get; set; } - public string? AreaName { get; set; } public TimeSpan? InspectionFrequency { get; set; } } } diff --git a/backend/api/Controllers/RobotController.cs b/backend/api/Controllers/RobotController.cs index 373c62179..5e1e1c954 100644 --- a/backend/api/Controllers/RobotController.cs +++ b/backend/api/Controllers/RobotController.cs @@ -218,14 +218,14 @@ [FromBody] UpdateRobotQuery query Robot updatedRobot; switch (fieldName) { - case "areaId": + case "currentInspectionAreaId": if (query.AreaId == null) - updatedRobot = await robotService.UpdateCurrentArea(id, null); + updatedRobot = await robotService.UpdateCurrentInspectionArea(id, null); else { var area = await areaService.ReadById(query.AreaId, readOnly: true); if (area == null) return NotFound($"No area with ID {query.AreaId} was found"); - updatedRobot = await robotService.UpdateCurrentArea(id, area.Id); + updatedRobot = await robotService.UpdateCurrentInspectionArea(id, area.Id); } break; case "pose": @@ -660,14 +660,6 @@ [FromRoute] string robotId return Conflict(errorMessage); } - try { await robotService.UpdateCurrentArea(robot.Id, null); } - catch (RobotNotFoundException) - { - string errorMessage = $"Failed to set current area to null for robot with id {robotId} because the robot was not found"; - logger.LogWarning("{Message}", errorMessage); - return NotFound(errorMessage); - } - return NoContent(); } } diff --git a/backend/api/Database/Context/FlotillaDbContext.cs b/backend/api/Database/Context/FlotillaDbContext.cs index 2566b2d59..9aef1595f 100644 --- a/backend/api/Database/Context/FlotillaDbContext.cs +++ b/backend/api/Database/Context/FlotillaDbContext.cs @@ -64,8 +64,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .Property(m => m.InspectionFrequency) .HasConversion(new TimeSpanToTicksConverter()); - modelBuilder.Entity().OwnsOne(m => m.Map).OwnsOne(t => t.TransformationMatrices); - modelBuilder.Entity().OwnsOne(m => m.Map).OwnsOne(b => b.Boundary); + modelBuilder.Entity().OwnsOne(m => m.Map).OwnsOne(t => t.TransformationMatrices); + modelBuilder.Entity().OwnsOne(m => m.Map).OwnsOne(b => b.Boundary); + modelBuilder.Entity().HasOne(m => m.InspectionArea).WithMany().OnDelete(DeleteBehavior.Restrict); + modelBuilder.Entity().HasOne(m => m.InspectionArea).WithMany().OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity().OwnsOne(r => r.Pose).OwnsOne(p => p.Orientation); modelBuilder.Entity().OwnsOne(r => r.Pose).OwnsOne(p => p.Position); diff --git a/backend/api/Database/Context/InitDb.cs b/backend/api/Database/Context/InitDb.cs index 81ca99f1c..c3d529ff0 100644 --- a/backend/api/Database/Context/InitDb.cs +++ b/backend/api/Database/Context/InitDb.cs @@ -173,7 +173,7 @@ private static List GetAreas() Id = Guid.NewGuid().ToString(), Deck = decks[0], Plant = decks[0].Plant, - Installation = decks[0].Plant!.Installation, + Installation = decks[0].Installation, Name = "testArea", MapMetadata = new MapMetadata(), DefaultLocalizationPose = new DefaultLocalizationPose(), @@ -184,7 +184,7 @@ private static List GetAreas() Id = Guid.NewGuid().ToString(), Deck = decks[0], Plant = decks[0].Plant, - Installation = decks[0].Plant!.Installation, + Installation = decks[0].Installation, Name = "testArea2", MapMetadata = new MapMetadata(), DefaultLocalizationPose = new DefaultLocalizationPose(), @@ -195,7 +195,7 @@ private static List GetAreas() Id = Guid.NewGuid().ToString(), Deck = decks[0], Plant = decks[0].Plant, - Installation = decks[0].Plant!.Installation, + Installation = decks[0].Installation, Name = "testArea3", MapMetadata = new MapMetadata(), DefaultLocalizationPose = new DefaultLocalizationPose(), @@ -206,7 +206,7 @@ private static List GetAreas() Id = Guid.NewGuid().ToString(), Deck = decks[1], Plant = decks[1].Plant, - Installation = decks[1].Plant.Installation, + Installation = decks[1].Installation, Name = "testArea4", MapMetadata = new MapMetadata(), DefaultLocalizationPose = new DefaultLocalizationPose(), @@ -217,7 +217,7 @@ private static List GetAreas() Id = Guid.NewGuid().ToString(), Deck = decks[2], Plant = decks[2].Plant, - Installation = decks[2].Plant.Installation, + Installation = decks[2].Installation, Name = "testArea5", MapMetadata = new MapMetadata(), DefaultLocalizationPose = new DefaultLocalizationPose(), @@ -228,7 +228,7 @@ private static List GetAreas() Id = Guid.NewGuid().ToString(), Deck = decks[3], Plant = decks[3].Plant, - Installation = decks[3].Plant.Installation, + Installation = decks[3].Installation, Name = "testArea6", MapMetadata = new MapMetadata(), DefaultLocalizationPose = new DefaultLocalizationPose(), @@ -239,7 +239,7 @@ private static List GetAreas() Id = Guid.NewGuid().ToString(), Deck = decks[4], Plant = decks[4].Plant, - Installation = decks[4].Plant.Installation, + Installation = decks[4].Installation, Name = "HB", MapMetadata = new MapMetadata(), DefaultLocalizationPose = new DefaultLocalizationPose(), @@ -250,7 +250,7 @@ private static List GetAreas() Id = Guid.NewGuid().ToString(), Deck = decks[5], Plant = decks[5].Plant, - Installation = decks[5].Plant.Installation, + Installation = decks[5].Installation, Name = "K-lab", MapMetadata = new MapMetadata(), DefaultLocalizationPose = new DefaultLocalizationPose(), @@ -349,8 +349,8 @@ private static List GetMissionDefinitions() { Id = Guid.NewGuid().ToString(), Name = "Placeholder Mission 1", - InstallationCode = areas[0].Installation!.InstallationCode, - Area = areas[0], + InstallationCode = decks[0].Installation!.InstallationCode, + InspectionArea = decks[0], Source = sources[0], Comment = "Interesting comment", InspectionFrequency = new DateTime().AddDays(12) - new DateTime(), @@ -361,8 +361,8 @@ private static List GetMissionDefinitions() { Id = Guid.NewGuid().ToString(), Name = "Placeholder Mission 2", - InstallationCode = areas[1].Installation!.InstallationCode, - Area = areas[1], + InstallationCode = decks[1].Installation!.InstallationCode, + InspectionArea = decks[1], Source = sources[1], InspectionFrequency = new DateTime().AddDays(7) - new DateTime(), LastSuccessfulRun = null @@ -372,8 +372,8 @@ private static List GetMissionDefinitions() { Id = Guid.NewGuid().ToString(), Name = "Placeholder Mission 3", - InstallationCode = areas[1].Installation!.InstallationCode, - Area = areas[1], + InstallationCode = decks[1].Installation!.InstallationCode, + InspectionArea = decks[1], Source = sources[2], LastSuccessfulRun = null }; @@ -382,9 +382,9 @@ private static List GetMissionDefinitions() { Id = Guid.NewGuid().ToString(), Name = "Placeholder Mission 4", - InstallationCode = areas[2].Installation.InstallationCode, + InstallationCode = decks[2].Installation.InstallationCode, InspectionFrequency = new DateTime().AddDays(90) - new DateTime(), - Area = areas[2], + InspectionArea = decks[2], Source = sources[2], LastSuccessfulRun = null }; @@ -393,9 +393,9 @@ private static List GetMissionDefinitions() { Id = Guid.NewGuid().ToString(), Name = "Placeholder Mission 5", - InstallationCode = areas[2].Installation.InstallationCode, + InstallationCode = decks[2].Installation.InstallationCode, InspectionFrequency = new DateTime().AddDays(35) - new DateTime(), - Area = areas[2], + InspectionArea = decks[2], Source = sources[2], LastSuccessfulRun = null }; @@ -404,9 +404,9 @@ private static List GetMissionDefinitions() { Id = Guid.NewGuid().ToString(), Name = "Placeholder Mission 6", - InstallationCode = areas[3].Installation.InstallationCode, + InstallationCode = decks[3].Installation.InstallationCode, InspectionFrequency = new DateTime().AddDays(4) - new DateTime(), - Area = areas[3], + InspectionArea = decks[3], Source = sources[2], LastSuccessfulRun = null }; @@ -414,8 +414,8 @@ private static List GetMissionDefinitions() { Id = Guid.NewGuid().ToString(), Name = "Placeholder Mission 7", - InstallationCode = areas[3].Installation.InstallationCode, - Area = areas[4], + InstallationCode = decks[3].Installation.InstallationCode, + InspectionArea = decks[4], Source = sources[2], LastSuccessfulRun = null }; @@ -530,26 +530,24 @@ private static List GetMissionRuns() { Name = "Placeholder Mission 1", Robot = robots[0], - InstallationCode = areas[0].Installation!.InstallationCode, - Area = areas[0], + InstallationCode = decks[0].Installation!.InstallationCode, + InspectionArea = decks[0], MissionId = missionDefinitions[0].Id, Status = MissionStatus.Successful, DesiredStartTime = DateTime.UtcNow, - Tasks = [], - Map = new MapMetadata() + Tasks = [] }; var missionRun2 = new MissionRun { Name = "Placeholder Mission 2", Robot = robots[1], - InstallationCode = areas[1].Installation!.InstallationCode, - Area = areas[1], + InstallationCode = decks[1].Installation!.InstallationCode, + InspectionArea = decks[1], MissionId = missionDefinitions[0].Id, Status = MissionStatus.Successful, DesiredStartTime = DateTime.UtcNow, - Tasks = [], - Map = new MapMetadata() + Tasks = [] }; missionDefinitions[0].LastSuccessfulRun = missionRun2; @@ -557,21 +555,20 @@ private static List GetMissionRuns() { Name = "Placeholder Mission 3", Robot = robots[2], - InstallationCode = areas[1].Installation!.InstallationCode, - Area = areas[1], + InstallationCode = decks[1].Installation!.InstallationCode, + InspectionArea = decks[1], MissionId = missionDefinitions[1].Id, Status = MissionStatus.Successful, DesiredStartTime = DateTime.UtcNow, - Tasks = [], - Map = new MapMetadata() + Tasks = [] }; var missionRun4 = new MissionRun { Name = "Placeholder Mission 4", Robot = robots[2], - InstallationCode = areas[1].Installation.InstallationCode, - Area = areas[1], + InstallationCode = decks[1].Installation.InstallationCode, + InspectionArea = decks[1], MissionId = missionDefinitions[1].Id, Status = MissionStatus.Failed, DesiredStartTime = DateTime.UtcNow, @@ -579,16 +576,15 @@ private static List GetMissionRuns() [ tasks[0], tasks[1] - ], - Map = new MapMetadata() + ] }; var missionRun5 = new MissionRun { Name = "Placeholder Mission 5", Robot = robots[2], - InstallationCode = areas[1].Installation.InstallationCode, - Area = areas[1], + InstallationCode = decks[1].Installation.InstallationCode, + InspectionArea = decks[1], MissionId = missionDefinitions[1].Id, Status = MissionStatus.PartiallySuccessful, DesiredStartTime = DateTime.UtcNow, @@ -596,16 +592,15 @@ private static List GetMissionRuns() [ tasks[0], tasks[2] - ], - Map = new MapMetadata() + ] }; var missionRun6 = new MissionRun { Name = "Placeholder Mission 6", Robot = robots[2], - InstallationCode = areas[1].Installation.InstallationCode, - Area = areas[1], + InstallationCode = decks[1].Installation.InstallationCode, + InspectionArea = decks[1], MissionId = missionDefinitions[1].Id, Status = MissionStatus.Cancelled, DesiredStartTime = DateTime.UtcNow, @@ -613,16 +608,15 @@ private static List GetMissionRuns() [ tasks[0], tasks[3] - ], - Map = new MapMetadata() + ] }; var missionRun7 = new MissionRun { Name = "Some failed tasks", Robot = robots[2], - InstallationCode = areas[1].Installation.InstallationCode, - Area = areas[1], + InstallationCode = decks[1].Installation.InstallationCode, + InspectionArea = decks[1], MissionId = missionDefinitions[1].Id, Status = MissionStatus.Failed, DesiredStartTime = DateTime.UtcNow, @@ -635,8 +629,7 @@ private static List GetMissionRuns() tasks[4], tasks[5], tasks[6] - ], - Map = new MapMetadata() + ] }; missionDefinitions[1].LastSuccessfulRun = missionRun3; diff --git a/backend/api/Database/Models/MissionDefinition.cs b/backend/api/Database/Models/MissionDefinition.cs index 63666c67d..42cef67ed 100644 --- a/backend/api/Database/Models/MissionDefinition.cs +++ b/backend/api/Database/Models/MissionDefinition.cs @@ -28,8 +28,9 @@ public class MissionDefinition : SortableRecord public virtual MissionRun? LastSuccessfulRun { get; set; } - [Required] - public Area Area { get; set; } + public Deck? InspectionArea { get; set; } + + public MapMetadata? Map { get; set; } public bool IsDeprecated { get; set; } } diff --git a/backend/api/Database/Models/MissionRun.cs b/backend/api/Database/Models/MissionRun.cs index 9beea9796..c2596830c 100644 --- a/backend/api/Database/Models/MissionRun.cs +++ b/backend/api/Database/Models/MissionRun.cs @@ -69,8 +69,7 @@ public IList Tasks [MaxLength(1000)] public string? Comment { get; set; } - [Required] - public Area Area { get; set; } + public Deck? InspectionArea { get; set; } public bool IsCompleted => _status @@ -80,8 +79,6 @@ or MissionStatus.Successful or MissionStatus.PartiallySuccessful or MissionStatus.Failed; - public MapMetadata? Map { get; set; } - public DateTime? StartTime { get; private set; } public DateTime? EndTime { get; private set; } @@ -174,8 +171,6 @@ public void CalculateEstimatedDuration() } } - public bool IsLocalizationMission() { return MissionRunType == MissionRunType.Localization; } - public bool IsReturnHomeMission() { return MissionRunType == MissionRunType.ReturnHome; } public bool IsEmergencyMission() { return MissionRunType == MissionRunType.Emergency; } @@ -197,7 +192,6 @@ public enum MissionRunType { Normal, ReturnHome, - Emergency, - Localization + Emergency } } diff --git a/backend/api/Database/Models/MissionTask.cs b/backend/api/Database/Models/MissionTask.cs index 8e7cde5d8..c4e290107 100644 --- a/backend/api/Database/Models/MissionTask.cs +++ b/backend/api/Database/Models/MissionTask.cs @@ -65,13 +65,6 @@ public MissionTask(Pose robotPose, MissionTaskType type) { switch (type) { - case MissionTaskType.Localization: - Type = type; - Description = "Localization"; - RobotPose = robotPose; - TaskOrder = 0; - Status = TaskStatus.NotStarted; - break; case MissionTaskType.ReturnHome: Type = type; Description = "Return to home"; @@ -88,7 +81,7 @@ public MissionTask(Pose robotPose, MissionTaskType type) Inspection = new Inspection(); break; default: - throw new MissionTaskNotFoundException("MissionTaskType should be Localization, ReturnHome or Inspection"); + throw new MissionTaskNotFoundException("MissionTaskType should be ReturnHome or Inspection"); } } @@ -169,7 +162,7 @@ or TaskStatus.Failed public void UpdateWithIsarInfo(IsarTask isarTask) { UpdateStatus(isarTask.TaskStatus); - if (isarTask.TaskType != IsarTaskType.ReturnToHome && isarTask.TaskType != IsarTaskType.Localize && isarTask.TaskType != IsarTaskType.MoveArm) + if (isarTask.TaskType != IsarTaskType.ReturnToHome && isarTask.TaskType != IsarTaskType.MoveArm) { Inspection?.UpdateWithIsarInfo(isarTask); } @@ -195,7 +188,6 @@ public static string ConvertMissionTaskTypeToIsarTaskType(MissionTaskType missio return missionTaskType switch { MissionTaskType.ReturnHome => "return_to_home", - MissionTaskType.Localization => "localization", MissionTaskType.Inspection => "inspection", _ => throw new ArgumentException($"ISAR Mission task type '{missionTaskType}' not supported"), }; @@ -237,7 +229,6 @@ public enum TaskStatus public enum MissionTaskType { Inspection, - Localization, ReturnHome } } diff --git a/backend/api/Database/Models/Robot.cs b/backend/api/Database/Models/Robot.cs index 2d43c8ac6..a57916c90 100644 --- a/backend/api/Database/Models/Robot.cs +++ b/backend/api/Database/Models/Robot.cs @@ -20,7 +20,7 @@ public Robot() Pose = new Pose(); } - public Robot(CreateRobotQuery createQuery, Installation installation, RobotModel model, Area? area = null) + public Robot(CreateRobotQuery createQuery, Installation installation, RobotModel model, Deck? inspectionArea = null) { var documentation = new List(); foreach (var documentQuery in createQuery.Documentation) @@ -37,7 +37,7 @@ public Robot(CreateRobotQuery createQuery, Installation installation, RobotModel Name = createQuery.Name; SerialNumber = createQuery.SerialNumber; CurrentInstallation = installation; - CurrentArea = area; + CurrentInspectionArea = inspectionArea; Documentation = documentation; Host = createQuery.Host; Port = createQuery.Port; @@ -70,7 +70,7 @@ public Robot(CreateRobotQuery createQuery, Installation installation, RobotModel [Required] public Installation CurrentInstallation { get; set; } - public Area? CurrentArea { get; set; } + public Deck? CurrentInspectionArea { get; set; } public float BatteryLevel { get; set; } diff --git a/backend/api/EventHandlers/InspectionFindingEventHandler.cs b/backend/api/EventHandlers/InspectionFindingEventHandler.cs index 6001de43d..70717f1c9 100644 --- a/backend/api/EventHandlers/InspectionFindingEventHandler.cs +++ b/backend/api/EventHandlers/InspectionFindingEventHandler.cs @@ -85,8 +85,8 @@ private async Task> GenerateFindingsList(List i { var finding = new Finding( task.TagId ?? "NA", - missionRun.Area?.Plant.Name ?? "NA", - missionRun.Area?.Name ?? "NA", + missionRun.InspectionArea?.Plant.Name ?? "NA", + missionRun.InspectionArea?.Name ?? "NA", inspectionFinding.Finding, inspectionFinding.InspectionDate ); @@ -112,7 +112,7 @@ public static string GenerateAdaptiveCard(string title, int numberOfFindings, Li var factsArray = new JArray( new JObject(new JProperty("name", "Anlegg"), new JProperty("value", finding.PlantName)), - new JObject(new JProperty("name", "Område"), new JProperty("value", finding.AreaName)), + new JObject(new JProperty("name", "Område"), new JProperty("value", finding.InspectionAreaName)), new JObject(new JProperty("name", "Tag Number"), new JProperty("value", finding.TagId)), new JObject(new JProperty("name", "Beskrivelse"), new JProperty("value", finding.FindingDescription)), new JObject(new JProperty("name", "Tidspunkt"), new JProperty("value", finding.Timestamp.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture))) @@ -169,11 +169,11 @@ public static string GetWebhookURL(IConfiguration configuration, string secretNa } } - public class Finding(string tagId, string plantName, string areaName, string findingDescription, DateTime timestamp) + public class Finding(string tagId, string plantName, string inspectionAreaName, string findingDescription, DateTime timestamp) { public string TagId { get; set; } = tagId ?? throw new ArgumentNullException(nameof(tagId)); public string PlantName { get; set; } = plantName ?? throw new ArgumentNullException(nameof(plantName)); - public string AreaName { get; set; } = areaName ?? throw new ArgumentNullException(nameof(areaName)); + public string InspectionAreaName { get; set; } = inspectionAreaName ?? throw new ArgumentNullException(nameof(inspectionAreaName)); public string FindingDescription { get; set; } = findingDescription ?? throw new ArgumentNullException(nameof(findingDescription)); public DateTime Timestamp { get; set; } = timestamp; } diff --git a/backend/api/EventHandlers/MissionEventHandler.cs b/backend/api/EventHandlers/MissionEventHandler.cs index 1fa0050e1..9a19a3535 100644 --- a/backend/api/EventHandlers/MissionEventHandler.cs +++ b/backend/api/EventHandlers/MissionEventHandler.cs @@ -1,5 +1,4 @@ -using Api.Controllers.Models; -using Api.Database.Models; +using Api.Database.Models; using Api.Services; using Api.Services.Events; using Api.Utilities; @@ -12,7 +11,6 @@ public class MissionEventHandler : EventHandlerBase // The mutex is used to ensure multiple missions aren't attempted scheduled simultaneously whenever multiple mission runs are created private readonly Semaphore _startMissionSemaphore = new(1, 1); - private readonly Semaphore _scheduleLocalizationSemaphore = new(1, 1); private readonly IServiceScopeFactory _scopeFactory; @@ -30,10 +28,6 @@ IServiceScopeFactory scopeFactory private IRobotService RobotService => _scopeFactory.CreateScope().ServiceProvider.GetRequiredService(); - private ILocalizationService LocalizationService => _scopeFactory.CreateScope().ServiceProvider.GetRequiredService(); - - private IAreaService AreaService => _scopeFactory.CreateScope().ServiceProvider.GetRequiredService(); - private IMissionSchedulingService MissionScheduling => _scopeFactory.CreateScope().ServiceProvider.GetRequiredService(); private ISignalRService SignalRService => _scopeFactory.CreateScope().ServiceProvider.GetRequiredService(); @@ -45,7 +39,6 @@ public override void Subscribe() { MissionRunService.MissionRunCreated += OnMissionRunCreated; MissionSchedulingService.RobotAvailable += OnRobotAvailable; - MissionSchedulingService.LocalizationMissionSuccessful += OnLocalizationMissionSuccessful; EmergencyActionService.SendRobotToDockTriggered += OnSendRobotToDockTriggered; EmergencyActionService.ReleaseRobotFromDockTriggered += OnReleaseRobotFromDockTriggered; } @@ -54,7 +47,6 @@ public override void Unsubscribe() { MissionRunService.MissionRunCreated -= OnMissionRunCreated; MissionSchedulingService.RobotAvailable -= OnRobotAvailable; - MissionSchedulingService.LocalizationMissionSuccessful -= OnLocalizationMissionSuccessful; EmergencyActionService.SendRobotToDockTriggered -= OnSendRobotToDockTriggered; EmergencyActionService.ReleaseRobotFromDockTriggered -= OnReleaseRobotFromDockTriggered; } @@ -75,20 +67,6 @@ private async void OnMissionRunCreated(object? sender, MissionRunCreatedEventArg return; } - _scheduleLocalizationSemaphore.WaitOne(); - if (!await LocalizationService.RobotIsLocalized(missionRun.Robot.Id)) - { - if (await MissionService.PendingLocalizationMissionRunExists(missionRun.Robot.Id) - || await MissionService.OngoingOrPausedLocalizationMissionRunExists(missionRun.Robot.Id)) - { - _scheduleLocalizationSemaphore.Release(); - return; - } - _logger.LogInformation("{Message}", $"Changing mission run with ID {missionRun.Id} to localization type"); - await MissionService.UpdateMissionRunType(missionRun.Id, MissionRunType.Localization); - } - _scheduleLocalizationSemaphore.Release(); - _startMissionSemaphore.WaitOne(); if (missionRun.MissionRunType != MissionRunType.ReturnHome && await ReturnToHomeService.GetActiveReturnToHomeMissionRun(missionRun.Robot.Id, readOnly: true) != null) @@ -96,7 +74,7 @@ private async void OnMissionRunCreated(object? sender, MissionRunCreatedEventArg await MissionScheduling.AbortActiveReturnToHomeMission(missionRun.Robot.Id); } - try { await MissionScheduling.StartNextMissionRunIfSystemIsAvailable(missionRun.Robot.Id); } + try { await MissionScheduling.StartNextMissionRunIfSystemIsAvailable(missionRun.Robot); } catch (MissionRunNotFoundException) { return; } finally { _startMissionSemaphore.Release(); } } @@ -112,38 +90,11 @@ private async void OnRobotAvailable(object? sender, RobotAvailableEventArgs e) } _startMissionSemaphore.WaitOne(); - try { await MissionScheduling.StartNextMissionRunIfSystemIsAvailable(robot.Id); } + try { await MissionScheduling.StartNextMissionRunIfSystemIsAvailable(robot); } catch (MissionRunNotFoundException) { return; } finally { _startMissionSemaphore.Release(); } } - private async void OnLocalizationMissionSuccessful(object? sender, LocalizationMissionSuccessfulEventArgs e) - { - _logger.LogInformation("Triggered LocalizationMissionSuccessful event for robot ID: {RobotId}", e.RobotId); - var robot = await RobotService.ReadById(e.RobotId, readOnly: true); - if (robot == null) - { - _logger.LogError("Robot with ID: {RobotId} was not found in the database", e.RobotId); - return; - } - - var lastMissionRun = await MissionService.ReadLastExecutedMissionRunByRobot(robot.Id, readOnly: true); - if (lastMissionRun != null) - { - if (lastMissionRun.MissionRunType == MissionRunType.Emergency & lastMissionRun.Status == MissionStatus.Successful) - { - _logger.LogInformation("Return to dock mission on robot {RobotName} was successful.", robot.Name); - SignalRService.ReportDockSuccessToSignalR(robot, $"Robot {robot.Name} is in the dock"); - } - } - - _startMissionSemaphore.WaitOne(); - try { await MissionScheduling.StartNextMissionRunIfSystemIsAvailable(robot.Id); } - catch (MissionRunNotFoundException) { return; } - catch (DatabaseUpdateException) { return; } - finally { _startMissionSemaphore.Release(); } - } - private async void OnSendRobotToDockTriggered(object? sender, RobotEmergencyEventArgs e) { _logger.LogInformation("Triggered EmergencyButtonPressed event for robot ID: {RobotId}", e.RobotId); @@ -170,30 +121,13 @@ private async void OnSendRobotToDockTriggered(object? sender, RobotEmergencyEven return; } - Area? area; - try - { - area = await FindRelevantRobotAreaForDockMission(robot.Id); - } - catch (RobotNotFoundException) - { - _logger.LogWarning( - "Failed to see if robot was localised. Could not find robot with ID '{RobotId}'", - e.RobotId - ); - return; - } - - if (area == null) { return; } - - try { await MissionScheduling.ScheduleMissionToDriveToDockPosition(e.RobotId, area.Id); } + try { await MissionScheduling.ScheduleMissionToDriveToDockPosition(e.RobotId); } catch (DockException ex) { _logger.LogError(ex, "Failed to schedule return to dock mission on robot {RobotName} because: {ErrorMessage}", robot.Name, ex.Message); SignalRService.ReportDockFailureToSignalR(robot, $"Failed to send {robot.Name} to a dock"); } - if (await MissionService.PendingOrOngoingLocalizationMissionRunExists(e.RobotId)) { return; } try { await MissionScheduling.StopCurrentMissionRun(e.RobotId); } catch (RobotNotFoundException) { return; } catch (MissionRunNotFoundException) @@ -219,7 +153,7 @@ private async void OnSendRobotToDockTriggered(object? sender, RobotEmergencyEven } _startMissionSemaphore.WaitOne(); - try { await MissionScheduling.StartNextMissionRunIfSystemIsAvailable(robot.Id); } + try { await MissionScheduling.StartNextMissionRunIfSystemIsAvailable(robot); } catch (MissionRunNotFoundException) { return; } finally { _startMissionSemaphore.Release(); } } @@ -240,9 +174,11 @@ private async void OnReleaseRobotFromDockTriggered(object? sender, RobotEmergenc return; } - try { await MissionScheduling.UnfreezeMissionRunQueueForRobot(e.RobotId); } + try { await MissionScheduling.UnfreezeMissionRunQueueForRobot(robot.Id); } catch (RobotNotFoundException) { return; } + robot.MissionQueueFrozen = false; + try { await RobotService.UpdateFlotillaStatus(e.RobotId, e.RobotFlotillaStatus ?? RobotFlotillaStatus.Normal); } catch (Exception ex) { @@ -251,53 +187,9 @@ private async void OnReleaseRobotFromDockTriggered(object? sender, RobotEmergenc } _startMissionSemaphore.WaitOne(); - try { await MissionScheduling.StartNextMissionRunIfSystemIsAvailable(robot.Id); } + try { await MissionScheduling.StartNextMissionRunIfSystemIsAvailable(robot); } catch (MissionRunNotFoundException) { return; } finally { _startMissionSemaphore.Release(); } } - - private async Task FindRelevantRobotAreaForDockMission(string robotId) - { - var robot = await RobotService.ReadById(robotId, readOnly: true); - if (robot == null) - { - _logger.LogError("Robot with ID: {RobotId} was not found in the database", robotId); - return null; - } - - if (!await LocalizationService.RobotIsLocalized(robotId)) - { - - if (await MissionService.PendingOrOngoingLocalizationMissionRunExists(robotId)) - { - var missionRuns = await MissionService.ReadAll( - new MissionRunQueryStringParameters - { - Statuses = [MissionStatus.Ongoing, MissionStatus.Pending], - RobotId = robot.Id, - OrderBy = "DesiredStartTime", - PageSize = 100 - }, readOnly: true); - - var localizationMission = missionRuns.Find(missionRun => missionRun.IsLocalizationMission()); - - return localizationMission?.Area ?? null; - } - - return null; - } - - var area = await AreaService.ReadById(robot.CurrentArea!.Id, readOnly: true); - if (area == null) - { - _logger.LogError("Could not find area with ID {AreaId}", robot.CurrentArea!.Id); - SignalRService.ReportDockFailureToSignalR(robot, $"Robot {robot.Name} was not correctly localised. Could not find area {robot.CurrentArea.Name}"); - return null; - } - - return area; - } - - } } diff --git a/backend/api/EventHandlers/MqttEventHandler.cs b/backend/api/EventHandlers/MqttEventHandler.cs index e39d85ec7..cbfe42986 100644 --- a/backend/api/EventHandlers/MqttEventHandler.cs +++ b/backend/api/EventHandlers/MqttEventHandler.cs @@ -89,15 +89,13 @@ private async void OnIsarStatus(object? sender, MqttReceivedArgs mqttArgs) if (robot.Status == isarStatus.Status) { return; } - if (await MissionRunService.OngoingOrPausedLocalizationMissionRunExists(robot.Id)) Thread.Sleep(5000); // Give localization mission update time to complete - var preUpdatedRobot = await RobotService.ReadByIsarId(isarStatus.IsarId, readOnly: true); if (preUpdatedRobot == null) { _logger.LogInformation("Received message from unknown ISAR instance {Id} with robot name {Name}", isarStatus.IsarId, isarStatus.RobotName); return; } - _logger.LogInformation("OnIsarStatus: Robot {robotName} has status {robotStatus} and current area {areaName}", preUpdatedRobot.Name, preUpdatedRobot.Status, preUpdatedRobot.CurrentArea?.Name); + _logger.LogInformation("OnIsarStatus: Robot {robotName} has status {robotStatus} and current inspection area {areaName}", preUpdatedRobot.Name, preUpdatedRobot.Status, preUpdatedRobot.CurrentInspectionArea?.Name); _updateRobotSemaphore.WaitOne(); _logger.LogDebug("Semaphore acquired for updating robot status"); @@ -110,7 +108,7 @@ private async void OnIsarStatus(object? sender, MqttReceivedArgs mqttArgs) _logger.LogInformation("Updated status for robot {Name} to {Status}", updatedRobot.Name, updatedRobot.Status); - _logger.LogInformation("OnIsarStatus: Robot {robotName} has status {robotStatus} and current area {areaName}", updatedRobot.Name, updatedRobot.Status, updatedRobot.CurrentArea?.Name); + _logger.LogInformation("OnIsarStatus: Robot {robotName} has status {robotStatus} and current inspection area {areaName}", updatedRobot.Name, updatedRobot.Status, updatedRobot.CurrentInspectionArea?.Name); if (isarStatus.Status == RobotStatus.Available) { @@ -131,11 +129,39 @@ private async void OnIsarStatus(object? sender, MqttReceivedArgs mqttArgs) _updateRobotSemaphore.Release(); _logger.LogDebug("Semaphore released after updating robot current mission id"); } - MissionScheduling.TriggerRobotAvailable(new RobotAvailableEventArgs(robot.Id)); + await MissionScheduling.StartNextMissionRunIfSystemIsAvailable(robot); + } + } + + private async void CreateRobot(IsarRobotInfoMessage isarRobotInfo, Installation installation) + { + _logger.LogInformation( + "Received message from new ISAR instance '{Id}' with robot name '{Name}'. Adding new robot to database", + isarRobotInfo.IsarId, isarRobotInfo.RobotName); + + var robotQuery = new CreateRobotQuery + { + IsarId = isarRobotInfo.IsarId, + Name = isarRobotInfo.RobotName, + RobotType = isarRobotInfo.RobotType, + SerialNumber = isarRobotInfo.SerialNumber, + CurrentInstallationCode = installation.InstallationCode, + Documentation = isarRobotInfo.DocumentationQueries, + Host = isarRobotInfo.Host, + Port = isarRobotInfo.Port, + RobotCapabilities = isarRobotInfo.Capabilities, + Status = RobotStatus.Available, + }; + + try + { + var newRobot = await RobotService.CreateFromQuery(robotQuery); + _logger.LogInformation("Added robot '{RobotName}' with ISAR id '{IsarId}' to database", newRobot.Name, newRobot.IsarId); } - else if (isarStatus.Status == RobotStatus.Offline) + catch (DbUpdateException) { - await RobotService.UpdateCurrentArea(robot.Id, null); + _logger.LogError($"Failed to add robot {robotQuery.Name} with to the database"); + return; } } @@ -160,35 +186,7 @@ private async void OnIsarRobotInfo(object? sender, MqttReceivedArgs mqttArgs) if (robot == null) { - _logger.LogInformation( - "Received message from new ISAR instance '{Id}' with robot name '{Name}'. Adding new robot to database", - isarRobotInfo.IsarId, isarRobotInfo.RobotName); - - var robotQuery = new CreateRobotQuery - { - IsarId = isarRobotInfo.IsarId, - Name = isarRobotInfo.RobotName, - RobotType = isarRobotInfo.RobotType, - SerialNumber = isarRobotInfo.SerialNumber, - CurrentInstallationCode = installation.InstallationCode, - Documentation = isarRobotInfo.DocumentationQueries, - Host = isarRobotInfo.Host, - Port = isarRobotInfo.Port, - RobotCapabilities = isarRobotInfo.Capabilities, - Status = RobotStatus.Available, - }; - - try - { - var newRobot = await RobotService.CreateFromQuery(robotQuery); - _logger.LogInformation("Added robot '{RobotName}' with ISAR id '{IsarId}' to database", newRobot.Name, newRobot.IsarId); - } - catch (DbUpdateException) - { - _logger.LogError($"Failed to add robot {robotQuery.Name} with to the database"); - return; - } - + CreateRobot(isarRobotInfo, installation); return; } @@ -273,57 +271,6 @@ private async void OnIsarMissionUpdate(object? sender, MqttReceivedArgs mqttArgs return; } - if (flotillaMissionRun.IsLocalizationMission()) - { - if (flotillaMissionRun.Tasks.Any((task) => task.Status == Database.Models.TaskStatus.Successful || task.Status == Database.Models.TaskStatus.PartiallySuccessful)) - { - try - { - _updateRobotSemaphore.WaitOne(); - _logger.LogDebug("Semaphore acquired for updating robot current area for localization mission successful"); - - var robotWithUpdatedArea = await RobotService.UpdateCurrentArea(flotillaMissionRun.Robot.Id, flotillaMissionRun.Area.Id); - } - catch (RobotNotFoundException) - { - _logger.LogError("Could not find robot '{RobotName}' with ID '{Id}'", flotillaMissionRun.Robot.Name, flotillaMissionRun.Robot.Id); - return; - } - finally - { - _updateRobotSemaphore.Release(); - _logger.LogDebug("Semaphore released after updating robot current area for localization mission successful"); - } - } - else if (flotillaMissionRun.Tasks.All((task) => task.Status == Database.Models.TaskStatus.Cancelled || task.Status == Database.Models.TaskStatus.Failed) || flotillaMissionRun.Status == MissionStatus.Aborted) - { - try - { - _updateRobotSemaphore.WaitOne(); - _logger.LogDebug("Semaphore acquired for updating robot current area for localization mission unsuccessful"); - - await RobotService.UpdateCurrentArea(flotillaMissionRun.Robot.Id, null); - - _logger.LogError("Localization mission run {MissionRunId} was unsuccessful on {RobotId}, scheduled missions will be aborted", flotillaMissionRun.Id, flotillaMissionRun.Robot.Id); - try { await MissionScheduling.AbortAllScheduledMissions(flotillaMissionRun.Robot.Id, "Aborted: Robot was not localized"); } - catch (RobotNotFoundException) { _logger.LogError("Failed to abort scheduled missions for robot {RobotId}", flotillaMissionRun.Robot.Id); } - } - catch (RobotNotFoundException) - { - _logger.LogError("Could not find robot '{RobotName}' with ID '{Id}'", flotillaMissionRun.Robot.Name, flotillaMissionRun.Robot.Id); - return; - } - finally - { - _updateRobotSemaphore.Release(); - _logger.LogDebug("Semaphore released after updating robot current area for localization mission unsuccessful"); - } - - SignalRService.ReportGeneralFailToSignalR(flotillaMissionRun.Robot, "Failed Localization Mission", $"Failed localization mission for robot {flotillaMissionRun.Robot.Name}."); - _logger.LogError("Localization mission for robot '{RobotName}' failed.", isarMission.RobotName); - } - } - if (flotillaMissionRun.Status == status) { return; } if (flotillaMissionRun.Status == MissionStatus.Aborted && status == MissionStatus.Cancelled) { status = MissionStatus.Aborted; } @@ -345,35 +292,8 @@ private async void OnIsarMissionUpdate(object? sender, MqttReceivedArgs mqttArgs return; } - if (updatedFlotillaMissionRun.IsReturnHomeMission() && (updatedFlotillaMissionRun.Status == MissionStatus.Cancelled || updatedFlotillaMissionRun.Status == MissionStatus.Failed)) - { - try - { - _updateRobotSemaphore.WaitOne(); - _logger.LogDebug("Semaphore acquired for updating robot current area"); - - await RobotService.UpdateCurrentArea(robot.Id, null); - } - catch (RobotNotFoundException) - { - _logger.LogError("Could not find robot '{RobotName}' with ID '{Id}'", robot.Name, robot.Id); - return; - } - finally - { - _updateRobotSemaphore.Release(); - _logger.LogDebug("Semaphore released after updating robot current area"); - } - } - _logger.LogInformation("Robot '{Id}' ('{Name}') - completed mission run {MissionRunId}", robot.IsarId, robot.Name, updatedFlotillaMissionRun.Id); - if (updatedFlotillaMissionRun.IsLocalizationMission() && (updatedFlotillaMissionRun.Status == MissionStatus.Successful || updatedFlotillaMissionRun.Status == MissionStatus.PartiallySuccessful)) - { - _logger.LogInformation("Triggering localization mission successful. The robot {robotName} have status {robotStatus} and current area {areaName}", robot.Name, robot.Status, robot.CurrentArea?.Name); - MissionScheduling.TriggerLocalizationMissionSuccessful(new LocalizationMissionSuccessfulEventArgs(robot.Id)); - } - if (updatedFlotillaMissionRun.MissionId == null) { _logger.LogInformation("Mission run {missionRunId} does not have a mission definition assosiated with it", updatedFlotillaMissionRun.Id); @@ -417,7 +337,7 @@ private async void OnIsarTaskUpdate(object? sender, MqttReceivedArgs mqttArgs) _logger.LogWarning("Mission run with ID {Id} was not found", task.MissionId); } - _ = SignalRService.SendMessageAsync("Mission run updated", missionRun?.Area?.Installation, missionRun != null ? new MissionRunResponse(missionRun) : null); + _ = SignalRService.SendMessageAsync("Mission run updated", missionRun?.InspectionArea?.Installation, missionRun != null ? new MissionRunResponse(missionRun) : null); _logger.LogInformation( "Task '{Id}' updated to '{Status}' for robot '{RobotName}' with ISAR id '{IsarId}'", task.TaskId, task.Status, task.RobotName, task.IsarId); diff --git a/backend/api/MQTT/MessageModels/IsarTask.cs b/backend/api/MQTT/MessageModels/IsarTask.cs index 68764cd34..10f022306 100644 --- a/backend/api/MQTT/MessageModels/IsarTask.cs +++ b/backend/api/MQTT/MessageModels/IsarTask.cs @@ -37,7 +37,6 @@ public MissionTaskType GetMissionTaskTypeFromIsarTask(string isarTaskType) "take_video" => MissionTaskType.Inspection, "take_thermal_image" => MissionTaskType.Inspection, "take_thermal_video" => MissionTaskType.Inspection, - "localize" => MissionTaskType.Localization, "return_to_home" => MissionTaskType.ReturnHome, _ => throw new ArgumentException($"ISAR Task type '{isarTaskType}' not supported") diff --git a/backend/api/Migrations/20241216130006_UseInspectionAreaInsteadOfAreaForLocalisation.Designer.cs b/backend/api/Migrations/20241216130006_UseInspectionAreaInsteadOfAreaForLocalisation.Designer.cs new file mode 100644 index 000000000..1de4fe8ce --- /dev/null +++ b/backend/api/Migrations/20241216130006_UseInspectionAreaInsteadOfAreaForLocalisation.Designer.cs @@ -0,0 +1,1333 @@ +// +using System; +using Api.Database.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Api.Migrations +{ + [DbContext(typeof(FlotillaDbContext))] + [Migration("20241216130006_UseInspectionAreaInsteadOfAreaForLocalisation")] + partial class UseInspectionAreaInsteadOfAreaForLocalisation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Api.Database.Models.AccessRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("AccessLevel") + .IsRequired() + .HasColumnType("text"); + + b.Property("InstallationId") + .HasColumnType("text"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InstallationId"); + + b.ToTable("AccessRoles"); + }); + + modelBuilder.Entity("Api.Database.Models.Area", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("DeckId") + .IsRequired() + .HasColumnType("text"); + + b.Property("DefaultLocalizationPoseId") + .HasColumnType("text"); + + b.Property("InstallationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PlantId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeckId"); + + b.HasIndex("DefaultLocalizationPoseId"); + + b.HasIndex("InstallationId"); + + b.HasIndex("PlantId"); + + b.ToTable("Areas"); + }); + + modelBuilder.Entity("Api.Database.Models.Deck", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("DefaultLocalizationPoseId") + .HasColumnType("text"); + + b.Property("InstallationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PlantId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DefaultLocalizationPoseId"); + + b.HasIndex("InstallationId"); + + b.HasIndex("PlantId"); + + b.ToTable("Decks"); + }); + + modelBuilder.Entity("Api.Database.Models.DefaultLocalizationPose", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("DockingEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DefaultLocalizationPoses"); + }); + + modelBuilder.Entity("Api.Database.Models.Inspection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("AnalysisType") + .HasColumnType("text"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("InspectionTargetName") + .HasColumnType("text"); + + b.Property("InspectionType") + .IsRequired() + .HasColumnType("text"); + + b.Property("InspectionUrl") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("IsarInspectionId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IsarTaskId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("VideoDuration") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.ToTable("Inspections"); + }); + + modelBuilder.Entity("Api.Database.Models.InspectionFinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Finding") + .IsRequired() + .HasColumnType("text"); + + b.Property("InspectionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InspectionId") + .HasColumnType("text"); + + b.Property("IsarTaskId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InspectionId"); + + b.ToTable("InspectionFindings"); + }); + + modelBuilder.Entity("Api.Database.Models.Installation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("InstallationCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("InstallationCode") + .IsUnique(); + + b.ToTable("Installations"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("InspectionAreaId") + .HasColumnType("text"); + + b.Property("InspectionFrequency") + .HasColumnType("bigint"); + + b.Property("InstallationCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeprecated") + .HasColumnType("boolean"); + + b.Property("LastSuccessfulRunId") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SourceId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InspectionAreaId"); + + b.HasIndex("LastSuccessfulRunId"); + + b.HasIndex("SourceId"); + + b.ToTable("MissionDefinitions"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Description") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("DesiredStartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EstimatedDuration") + .HasColumnType("bigint"); + + b.Property("InspectionAreaId") + .HasColumnType("text"); + + b.Property("InstallationCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IsDeprecated") + .HasColumnType("boolean"); + + b.Property("IsarMissionId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("MissionId") + .HasColumnType("text"); + + b.Property("MissionRunType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RobotId") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StatusReason") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.HasKey("Id"); + + b.HasIndex("InspectionAreaId"); + + b.HasIndex("RobotId"); + + b.ToTable("MissionRuns"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("InspectionId") + .HasColumnType("text"); + + b.Property("IsarTaskId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("MissionRunId") + .HasColumnType("text"); + + b.Property("PoseId") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TagId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TagLink") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TaskOrder") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InspectionId"); + + b.HasIndex("MissionRunId"); + + b.ToTable("MissionTasks"); + }); + + modelBuilder.Entity("Api.Database.Models.Plant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("InstallationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PlantCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.HasKey("Id"); + + b.HasIndex("InstallationId"); + + b.HasIndex("PlantCode") + .IsUnique(); + + b.ToTable("Plants"); + }); + + modelBuilder.Entity("Api.Database.Models.Robot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("BatteryLevel") + .HasColumnType("real"); + + b.Property("CurrentInspectionAreaId") + .HasColumnType("text"); + + b.Property("CurrentInstallationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentMissionId") + .HasColumnType("text"); + + b.Property("Deprecated") + .HasColumnType("boolean"); + + b.Property("FlotillaStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("Host") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IsarConnected") + .HasColumnType("boolean"); + + b.Property("IsarId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("MissionQueueFrozen") + .HasColumnType("boolean"); + + b.Property("ModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("PressureLevel") + .HasColumnType("real"); + + b.Property("RobotCapabilities") + .HasColumnType("text"); + + b.Property("SerialNumber") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CurrentInspectionAreaId"); + + b.HasIndex("CurrentInstallationId"); + + b.HasIndex("ModelId"); + + b.ToTable("Robots"); + }); + + modelBuilder.Entity("Api.Database.Models.RobotBatteryTimeseries", b => + { + b.Property("BatteryLevel") + .HasColumnType("real"); + + b.Property("MissionId") + .HasColumnType("text"); + + b.Property("RobotId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.ToTable("RobotBatteryTimeseries"); + }); + + modelBuilder.Entity("Api.Database.Models.RobotModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("AverageDurationPerTag") + .HasColumnType("real"); + + b.Property("BatteryMissionStartThreshold") + .HasColumnType("real"); + + b.Property("BatteryWarningThreshold") + .HasColumnType("real"); + + b.Property("LowerPressureWarningThreshold") + .HasColumnType("real"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpperPressureWarningThreshold") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("Type") + .IsUnique(); + + b.ToTable("RobotModels"); + }); + + modelBuilder.Entity("Api.Database.Models.RobotPoseTimeseries", b => + { + b.Property("MissionId") + .HasColumnType("text"); + + b.Property("OrientationW") + .HasColumnType("real"); + + b.Property("OrientationX") + .HasColumnType("real"); + + b.Property("OrientationY") + .HasColumnType("real"); + + b.Property("OrientationZ") + .HasColumnType("real"); + + b.Property("PositionX") + .HasColumnType("real"); + + b.Property("PositionY") + .HasColumnType("real"); + + b.Property("PositionZ") + .HasColumnType("real"); + + b.Property("RobotId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.ToTable("RobotPoseTimeseries"); + }); + + modelBuilder.Entity("Api.Database.Models.RobotPressureTimeseries", b => + { + b.Property("MissionId") + .HasColumnType("text"); + + b.Property("Pressure") + .HasColumnType("real"); + + b.Property("RobotId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.ToTable("RobotPressureTimeseries"); + }); + + modelBuilder.Entity("Api.Database.Models.Source", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("CustomMissionTasks") + .HasColumnType("text"); + + b.Property("SourceId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Sources"); + }); + + modelBuilder.Entity("Api.Database.Models.UserInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Oid") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("UserInfos"); + }); + + modelBuilder.Entity("Api.Services.MissionLoaders.TagInspectionMetadata", b => + { + b.Property("TagId") + .HasColumnType("text"); + + b.HasKey("TagId"); + + b.ToTable("TagInspectionMetadata"); + }); + + modelBuilder.Entity("Api.Database.Models.AccessRole", b => + { + b.HasOne("Api.Database.Models.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId"); + + b.Navigation("Installation"); + }); + + modelBuilder.Entity("Api.Database.Models.Area", b => + { + b.HasOne("Api.Database.Models.Deck", "Deck") + .WithMany() + .HasForeignKey("DeckId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Api.Database.Models.DefaultLocalizationPose", "DefaultLocalizationPose") + .WithMany() + .HasForeignKey("DefaultLocalizationPoseId"); + + b.HasOne("Api.Database.Models.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Api.Database.Models.Plant", "Plant") + .WithMany() + .HasForeignKey("PlantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.OwnsOne("Api.Database.Models.MapMetadata", "MapMetadata", b1 => + { + b1.Property("AreaId") + .HasColumnType("text"); + + b1.Property("MapName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b1.HasKey("AreaId"); + + b1.ToTable("Areas"); + + b1.WithOwner() + .HasForeignKey("AreaId"); + + b1.OwnsOne("Api.Database.Models.Boundary", "Boundary", b2 => + { + b2.Property("MapMetadataAreaId") + .HasColumnType("text"); + + b2.Property("X1") + .HasColumnType("double precision"); + + b2.Property("X2") + .HasColumnType("double precision"); + + b2.Property("Y1") + .HasColumnType("double precision"); + + b2.Property("Y2") + .HasColumnType("double precision"); + + b2.Property("Z1") + .HasColumnType("double precision"); + + b2.Property("Z2") + .HasColumnType("double precision"); + + b2.HasKey("MapMetadataAreaId"); + + b2.ToTable("Areas"); + + b2.WithOwner() + .HasForeignKey("MapMetadataAreaId"); + }); + + b1.OwnsOne("Api.Database.Models.TransformationMatrices", "TransformationMatrices", b2 => + { + b2.Property("MapMetadataAreaId") + .HasColumnType("text"); + + b2.Property("C1") + .HasColumnType("double precision"); + + b2.Property("C2") + .HasColumnType("double precision"); + + b2.Property("D1") + .HasColumnType("double precision"); + + b2.Property("D2") + .HasColumnType("double precision"); + + b2.HasKey("MapMetadataAreaId"); + + b2.ToTable("Areas"); + + b2.WithOwner() + .HasForeignKey("MapMetadataAreaId"); + }); + + b1.Navigation("Boundary") + .IsRequired(); + + b1.Navigation("TransformationMatrices") + .IsRequired(); + }); + + b.Navigation("Deck"); + + b.Navigation("DefaultLocalizationPose"); + + b.Navigation("Installation"); + + b.Navigation("MapMetadata") + .IsRequired(); + + b.Navigation("Plant"); + }); + + modelBuilder.Entity("Api.Database.Models.Deck", b => + { + b.HasOne("Api.Database.Models.DefaultLocalizationPose", "DefaultLocalizationPose") + .WithMany() + .HasForeignKey("DefaultLocalizationPoseId"); + + b.HasOne("Api.Database.Models.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Api.Database.Models.Plant", "Plant") + .WithMany() + .HasForeignKey("PlantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DefaultLocalizationPose"); + + b.Navigation("Installation"); + + b.Navigation("Plant"); + }); + + modelBuilder.Entity("Api.Database.Models.DefaultLocalizationPose", b => + { + b.OwnsOne("Api.Database.Models.Pose", "Pose", b1 => + { + b1.Property("DefaultLocalizationPoseId") + .HasColumnType("text"); + + b1.HasKey("DefaultLocalizationPoseId"); + + b1.ToTable("DefaultLocalizationPoses"); + + b1.WithOwner() + .HasForeignKey("DefaultLocalizationPoseId"); + + b1.OwnsOne("Api.Database.Models.Orientation", "Orientation", b2 => + { + b2.Property("PoseDefaultLocalizationPoseId") + .HasColumnType("text"); + + b2.Property("W") + .HasColumnType("real"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseDefaultLocalizationPoseId"); + + b2.ToTable("DefaultLocalizationPoses"); + + b2.WithOwner() + .HasForeignKey("PoseDefaultLocalizationPoseId"); + }); + + b1.OwnsOne("Api.Database.Models.Position", "Position", b2 => + { + b2.Property("PoseDefaultLocalizationPoseId") + .HasColumnType("text"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseDefaultLocalizationPoseId"); + + b2.ToTable("DefaultLocalizationPoses"); + + b2.WithOwner() + .HasForeignKey("PoseDefaultLocalizationPoseId"); + }); + + b1.Navigation("Orientation") + .IsRequired(); + + b1.Navigation("Position") + .IsRequired(); + }); + + b.Navigation("Pose") + .IsRequired(); + }); + + modelBuilder.Entity("Api.Database.Models.Inspection", b => + { + b.OwnsOne("Api.Database.Models.Position", "InspectionTarget", b1 => + { + b1.Property("InspectionId") + .HasColumnType("text"); + + b1.Property("X") + .HasColumnType("real"); + + b1.Property("Y") + .HasColumnType("real"); + + b1.Property("Z") + .HasColumnType("real"); + + b1.HasKey("InspectionId"); + + b1.ToTable("Inspections"); + + b1.WithOwner() + .HasForeignKey("InspectionId"); + }); + + b.Navigation("InspectionTarget") + .IsRequired(); + }); + + modelBuilder.Entity("Api.Database.Models.InspectionFinding", b => + { + b.HasOne("Api.Database.Models.Inspection", null) + .WithMany("InspectionFindings") + .HasForeignKey("InspectionId"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionDefinition", b => + { + b.HasOne("Api.Database.Models.Deck", "InspectionArea") + .WithMany() + .HasForeignKey("InspectionAreaId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Api.Database.Models.MissionRun", "LastSuccessfulRun") + .WithMany() + .HasForeignKey("LastSuccessfulRunId"); + + b.HasOne("Api.Database.Models.Source", "Source") + .WithMany() + .HasForeignKey("SourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Api.Database.Models.MapMetadata", "Map", b1 => + { + b1.Property("MissionDefinitionId") + .HasColumnType("text"); + + b1.Property("MapName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b1.HasKey("MissionDefinitionId"); + + b1.ToTable("MissionDefinitions"); + + b1.WithOwner() + .HasForeignKey("MissionDefinitionId"); + + b1.OwnsOne("Api.Database.Models.Boundary", "Boundary", b2 => + { + b2.Property("MapMetadataMissionDefinitionId") + .HasColumnType("text"); + + b2.Property("X1") + .HasColumnType("double precision"); + + b2.Property("X2") + .HasColumnType("double precision"); + + b2.Property("Y1") + .HasColumnType("double precision"); + + b2.Property("Y2") + .HasColumnType("double precision"); + + b2.Property("Z1") + .HasColumnType("double precision"); + + b2.Property("Z2") + .HasColumnType("double precision"); + + b2.HasKey("MapMetadataMissionDefinitionId"); + + b2.ToTable("MissionDefinitions"); + + b2.WithOwner() + .HasForeignKey("MapMetadataMissionDefinitionId"); + }); + + b1.OwnsOne("Api.Database.Models.TransformationMatrices", "TransformationMatrices", b2 => + { + b2.Property("MapMetadataMissionDefinitionId") + .HasColumnType("text"); + + b2.Property("C1") + .HasColumnType("double precision"); + + b2.Property("C2") + .HasColumnType("double precision"); + + b2.Property("D1") + .HasColumnType("double precision"); + + b2.Property("D2") + .HasColumnType("double precision"); + + b2.HasKey("MapMetadataMissionDefinitionId"); + + b2.ToTable("MissionDefinitions"); + + b2.WithOwner() + .HasForeignKey("MapMetadataMissionDefinitionId"); + }); + + b1.Navigation("Boundary") + .IsRequired(); + + b1.Navigation("TransformationMatrices") + .IsRequired(); + }); + + b.Navigation("InspectionArea"); + + b.Navigation("LastSuccessfulRun"); + + b.Navigation("Map"); + + b.Navigation("Source"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionRun", b => + { + b.HasOne("Api.Database.Models.Deck", "InspectionArea") + .WithMany() + .HasForeignKey("InspectionAreaId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Api.Database.Models.Robot", "Robot") + .WithMany() + .HasForeignKey("RobotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InspectionArea"); + + b.Navigation("Robot"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionTask", b => + { + b.HasOne("Api.Database.Models.Inspection", "Inspection") + .WithMany() + .HasForeignKey("InspectionId"); + + b.HasOne("Api.Database.Models.MissionRun", null) + .WithMany("Tasks") + .HasForeignKey("MissionRunId"); + + b.OwnsOne("Api.Services.Models.IsarZoomDescription", "IsarZoomDescription", b1 => + { + b1.Property("MissionTaskId") + .HasColumnType("text"); + + b1.Property("ObjectHeight") + .HasColumnType("double precision") + .HasAnnotation("Relational:JsonPropertyName", "objectHeight"); + + b1.Property("ObjectWidth") + .HasColumnType("double precision") + .HasAnnotation("Relational:JsonPropertyName", "objectWidth"); + + b1.HasKey("MissionTaskId"); + + b1.ToTable("MissionTasks"); + + b1.WithOwner() + .HasForeignKey("MissionTaskId"); + }); + + b.OwnsOne("Api.Database.Models.Pose", "RobotPose", b1 => + { + b1.Property("MissionTaskId") + .HasColumnType("text"); + + b1.HasKey("MissionTaskId"); + + b1.ToTable("MissionTasks"); + + b1.WithOwner() + .HasForeignKey("MissionTaskId"); + + b1.OwnsOne("Api.Database.Models.Orientation", "Orientation", b2 => + { + b2.Property("PoseMissionTaskId") + .HasColumnType("text"); + + b2.Property("W") + .HasColumnType("real"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseMissionTaskId"); + + b2.ToTable("MissionTasks"); + + b2.WithOwner() + .HasForeignKey("PoseMissionTaskId"); + }); + + b1.OwnsOne("Api.Database.Models.Position", "Position", b2 => + { + b2.Property("PoseMissionTaskId") + .HasColumnType("text"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseMissionTaskId"); + + b2.ToTable("MissionTasks"); + + b2.WithOwner() + .HasForeignKey("PoseMissionTaskId"); + }); + + b1.Navigation("Orientation") + .IsRequired(); + + b1.Navigation("Position") + .IsRequired(); + }); + + b.Navigation("Inspection"); + + b.Navigation("IsarZoomDescription"); + + b.Navigation("RobotPose") + .IsRequired(); + }); + + modelBuilder.Entity("Api.Database.Models.Plant", b => + { + b.HasOne("Api.Database.Models.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Installation"); + }); + + modelBuilder.Entity("Api.Database.Models.Robot", b => + { + b.HasOne("Api.Database.Models.Deck", "CurrentInspectionArea") + .WithMany() + .HasForeignKey("CurrentInspectionAreaId"); + + b.HasOne("Api.Database.Models.Installation", "CurrentInstallation") + .WithMany() + .HasForeignKey("CurrentInstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Api.Database.Models.RobotModel", "Model") + .WithMany() + .HasForeignKey("ModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Api.Database.Models.DocumentInfo", "Documentation", b1 => + { + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b1.Property("RobotId") + .IsRequired() + .HasColumnType("text"); + + b1.Property("Url") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b1.HasKey("Id"); + + b1.HasIndex("RobotId"); + + b1.ToTable("DocumentInfo"); + + b1.WithOwner() + .HasForeignKey("RobotId"); + }); + + b.OwnsOne("Api.Database.Models.Pose", "Pose", b1 => + { + b1.Property("RobotId") + .HasColumnType("text"); + + b1.HasKey("RobotId"); + + b1.ToTable("Robots"); + + b1.WithOwner() + .HasForeignKey("RobotId"); + + b1.OwnsOne("Api.Database.Models.Orientation", "Orientation", b2 => + { + b2.Property("PoseRobotId") + .HasColumnType("text"); + + b2.Property("W") + .HasColumnType("real"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseRobotId"); + + b2.ToTable("Robots"); + + b2.WithOwner() + .HasForeignKey("PoseRobotId"); + }); + + b1.OwnsOne("Api.Database.Models.Position", "Position", b2 => + { + b2.Property("PoseRobotId") + .HasColumnType("text"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseRobotId"); + + b2.ToTable("Robots"); + + b2.WithOwner() + .HasForeignKey("PoseRobotId"); + }); + + b1.Navigation("Orientation") + .IsRequired(); + + b1.Navigation("Position") + .IsRequired(); + }); + + b.Navigation("CurrentInspectionArea"); + + b.Navigation("CurrentInstallation"); + + b.Navigation("Documentation"); + + b.Navigation("Model"); + + b.Navigation("Pose") + .IsRequired(); + }); + + modelBuilder.Entity("Api.Services.MissionLoaders.TagInspectionMetadata", b => + { + b.OwnsOne("Api.Services.Models.IsarZoomDescription", "ZoomDescription", b1 => + { + b1.Property("TagInspectionMetadataTagId") + .HasColumnType("text"); + + b1.Property("ObjectHeight") + .HasColumnType("double precision") + .HasAnnotation("Relational:JsonPropertyName", "objectHeight"); + + b1.Property("ObjectWidth") + .HasColumnType("double precision") + .HasAnnotation("Relational:JsonPropertyName", "objectWidth"); + + b1.HasKey("TagInspectionMetadataTagId"); + + b1.ToTable("TagInspectionMetadata"); + + b1.WithOwner() + .HasForeignKey("TagInspectionMetadataTagId"); + }); + + b.Navigation("ZoomDescription"); + }); + + modelBuilder.Entity("Api.Database.Models.Inspection", b => + { + b.Navigation("InspectionFindings"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionRun", b => + { + b.Navigation("Tasks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/api/Migrations/20241216130006_UseInspectionAreaInsteadOfAreaForLocalisation.cs b/backend/api/Migrations/20241216130006_UseInspectionAreaInsteadOfAreaForLocalisation.cs new file mode 100644 index 000000000..145f7ffa7 --- /dev/null +++ b/backend/api/Migrations/20241216130006_UseInspectionAreaInsteadOfAreaForLocalisation.cs @@ -0,0 +1,408 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Api.Migrations +{ + /// + public partial class UseInspectionAreaInsteadOfAreaForLocalisation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_MissionDefinitions_Areas_AreaId", + table: "MissionDefinitions"); + + migrationBuilder.DropForeignKey( + name: "FK_MissionRuns_Areas_AreaId", + table: "MissionRuns"); + + migrationBuilder.DropForeignKey( + name: "FK_Robots_Areas_CurrentAreaId", + table: "Robots"); + + migrationBuilder.DropIndex( + name: "IX_MissionRuns_AreaId", + table: "MissionRuns"); + + migrationBuilder.DropIndex( + name: "IX_MissionDefinitions_AreaId", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "AreaId", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_X1", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_X2", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_Y1", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_Y2", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_Z1", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_Z2", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_MapName", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_TransformationMatrices_C1", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_TransformationMatrices_C2", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_TransformationMatrices_D1", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "Map_TransformationMatrices_D2", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "AreaId", + table: "MissionDefinitions"); + + migrationBuilder.RenameColumn( + name: "CurrentAreaId", + table: "Robots", + newName: "CurrentInspectionAreaId"); + + migrationBuilder.RenameIndex( + name: "IX_Robots_CurrentAreaId", + table: "Robots", + newName: "IX_Robots_CurrentInspectionAreaId"); + + migrationBuilder.AddColumn( + name: "InspectionAreaId", + table: "MissionRuns", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "InspectionAreaId", + table: "MissionDefinitions", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_X1", + table: "MissionDefinitions", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_X2", + table: "MissionDefinitions", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_Y1", + table: "MissionDefinitions", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_Y2", + table: "MissionDefinitions", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_Z1", + table: "MissionDefinitions", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_Z2", + table: "MissionDefinitions", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_MapName", + table: "MissionDefinitions", + type: "character varying(200)", + maxLength: 200, + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_TransformationMatrices_C1", + table: "MissionDefinitions", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_TransformationMatrices_C2", + table: "MissionDefinitions", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_TransformationMatrices_D1", + table: "MissionDefinitions", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_TransformationMatrices_D2", + table: "MissionDefinitions", + type: "double precision", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_MissionRuns_InspectionAreaId", + table: "MissionRuns", + column: "InspectionAreaId"); + + migrationBuilder.CreateIndex( + name: "IX_MissionDefinitions_InspectionAreaId", + table: "MissionDefinitions", + column: "InspectionAreaId"); + + migrationBuilder.AddForeignKey( + name: "FK_MissionDefinitions_Decks_InspectionAreaId", + table: "MissionDefinitions", + column: "InspectionAreaId", + principalTable: "Decks", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_MissionRuns_Decks_InspectionAreaId", + table: "MissionRuns", + column: "InspectionAreaId", + principalTable: "Decks", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Robots_Decks_CurrentInspectionAreaId", + table: "Robots", + column: "CurrentInspectionAreaId", + principalTable: "Decks", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_MissionDefinitions_Decks_InspectionAreaId", + table: "MissionDefinitions"); + + migrationBuilder.DropForeignKey( + name: "FK_MissionRuns_Decks_InspectionAreaId", + table: "MissionRuns"); + + migrationBuilder.DropForeignKey( + name: "FK_Robots_Decks_CurrentInspectionAreaId", + table: "Robots"); + + migrationBuilder.DropIndex( + name: "IX_MissionRuns_InspectionAreaId", + table: "MissionRuns"); + + migrationBuilder.DropIndex( + name: "IX_MissionDefinitions_InspectionAreaId", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "InspectionAreaId", + table: "MissionRuns"); + + migrationBuilder.DropColumn( + name: "InspectionAreaId", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_X1", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_X2", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_Y1", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_Y2", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_Z1", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_Boundary_Z2", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_MapName", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_TransformationMatrices_C1", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_TransformationMatrices_C2", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_TransformationMatrices_D1", + table: "MissionDefinitions"); + + migrationBuilder.DropColumn( + name: "Map_TransformationMatrices_D2", + table: "MissionDefinitions"); + + migrationBuilder.RenameColumn( + name: "CurrentInspectionAreaId", + table: "Robots", + newName: "CurrentAreaId"); + + migrationBuilder.RenameIndex( + name: "IX_Robots_CurrentInspectionAreaId", + table: "Robots", + newName: "IX_Robots_CurrentAreaId"); + + migrationBuilder.AddColumn( + name: "AreaId", + table: "MissionRuns", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "Map_Boundary_X1", + table: "MissionRuns", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_X2", + table: "MissionRuns", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_Y1", + table: "MissionRuns", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_Y2", + table: "MissionRuns", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_Z1", + table: "MissionRuns", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_Boundary_Z2", + table: "MissionRuns", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_MapName", + table: "MissionRuns", + type: "character varying(200)", + maxLength: 200, + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_TransformationMatrices_C1", + table: "MissionRuns", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_TransformationMatrices_C2", + table: "MissionRuns", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_TransformationMatrices_D1", + table: "MissionRuns", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "Map_TransformationMatrices_D2", + table: "MissionRuns", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "AreaId", + table: "MissionDefinitions", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.CreateIndex( + name: "IX_MissionRuns_AreaId", + table: "MissionRuns", + column: "AreaId"); + + migrationBuilder.CreateIndex( + name: "IX_MissionDefinitions_AreaId", + table: "MissionDefinitions", + column: "AreaId"); + + migrationBuilder.AddForeignKey( + name: "FK_MissionDefinitions_Areas_AreaId", + table: "MissionDefinitions", + column: "AreaId", + principalTable: "Areas", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_MissionRuns_Areas_AreaId", + table: "MissionRuns", + column: "AreaId", + principalTable: "Areas", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Robots_Areas_CurrentAreaId", + table: "Robots", + column: "CurrentAreaId", + principalTable: "Areas", + principalColumn: "Id"); + } + } +} diff --git a/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs b/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs index d983d30c7..2a5a2472c 100644 --- a/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs +++ b/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs @@ -237,14 +237,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("text"); - b.Property("AreaId") - .IsRequired() - .HasColumnType("text"); - b.Property("Comment") .HasMaxLength(1000) .HasColumnType("character varying(1000)"); + b.Property("InspectionAreaId") + .HasColumnType("text"); + b.Property("InspectionFrequency") .HasColumnType("bigint"); @@ -269,7 +268,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("AreaId"); + b.HasIndex("InspectionAreaId"); b.HasIndex("LastSuccessfulRunId"); @@ -284,10 +283,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("text"); - b.Property("AreaId") - .IsRequired() - .HasColumnType("text"); - b.Property("Comment") .HasMaxLength(1000) .HasColumnType("character varying(1000)"); @@ -305,6 +300,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("EstimatedDuration") .HasColumnType("bigint"); + b.Property("InspectionAreaId") + .HasColumnType("text"); + b.Property("InstallationCode") .IsRequired() .HasMaxLength(200) @@ -346,7 +344,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("AreaId"); + b.HasIndex("InspectionAreaId"); b.HasIndex("RobotId"); @@ -449,7 +447,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("BatteryLevel") .HasColumnType("real"); - b.Property("CurrentAreaId") + b.Property("CurrentInspectionAreaId") .HasColumnType("text"); b.Property("CurrentInstallationId") @@ -511,7 +509,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("CurrentAreaId"); + b.HasIndex("CurrentInspectionAreaId"); b.HasIndex("CurrentInstallationId"); @@ -927,11 +925,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Api.Database.Models.MissionDefinition", b => { - b.HasOne("Api.Database.Models.Area", "Area") + b.HasOne("Api.Database.Models.Deck", "InspectionArea") .WithMany() - .HasForeignKey("AreaId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("InspectionAreaId") + .OnDelete(DeleteBehavior.Restrict); b.HasOne("Api.Database.Models.MissionRun", "LastSuccessfulRun") .WithMany() @@ -943,30 +940,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Area"); - - b.Navigation("LastSuccessfulRun"); - - b.Navigation("Source"); - }); - - modelBuilder.Entity("Api.Database.Models.MissionRun", b => - { - b.HasOne("Api.Database.Models.Area", "Area") - .WithMany() - .HasForeignKey("AreaId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Api.Database.Models.Robot", "Robot") - .WithMany() - .HasForeignKey("RobotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - b.OwnsOne("Api.Database.Models.MapMetadata", "Map", b1 => { - b1.Property("MissionRunId") + b1.Property("MissionDefinitionId") .HasColumnType("text"); b1.Property("MapName") @@ -974,16 +950,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(200) .HasColumnType("character varying(200)"); - b1.HasKey("MissionRunId"); + b1.HasKey("MissionDefinitionId"); - b1.ToTable("MissionRuns"); + b1.ToTable("MissionDefinitions"); b1.WithOwner() - .HasForeignKey("MissionRunId"); + .HasForeignKey("MissionDefinitionId"); b1.OwnsOne("Api.Database.Models.Boundary", "Boundary", b2 => { - b2.Property("MapMetadataMissionRunId") + b2.Property("MapMetadataMissionDefinitionId") .HasColumnType("text"); b2.Property("X1") @@ -1004,17 +980,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.Property("Z2") .HasColumnType("double precision"); - b2.HasKey("MapMetadataMissionRunId"); + b2.HasKey("MapMetadataMissionDefinitionId"); - b2.ToTable("MissionRuns"); + b2.ToTable("MissionDefinitions"); b2.WithOwner() - .HasForeignKey("MapMetadataMissionRunId"); + .HasForeignKey("MapMetadataMissionDefinitionId"); }); b1.OwnsOne("Api.Database.Models.TransformationMatrices", "TransformationMatrices", b2 => { - b2.Property("MapMetadataMissionRunId") + b2.Property("MapMetadataMissionDefinitionId") .HasColumnType("text"); b2.Property("C1") @@ -1029,12 +1005,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.Property("D2") .HasColumnType("double precision"); - b2.HasKey("MapMetadataMissionRunId"); + b2.HasKey("MapMetadataMissionDefinitionId"); - b2.ToTable("MissionRuns"); + b2.ToTable("MissionDefinitions"); b2.WithOwner() - .HasForeignKey("MapMetadataMissionRunId"); + .HasForeignKey("MapMetadataMissionDefinitionId"); }); b1.Navigation("Boundary") @@ -1044,10 +1020,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); - b.Navigation("Area"); + b.Navigation("InspectionArea"); + + b.Navigation("LastSuccessfulRun"); b.Navigation("Map"); + b.Navigation("Source"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionRun", b => + { + b.HasOne("Api.Database.Models.Deck", "InspectionArea") + .WithMany() + .HasForeignKey("InspectionAreaId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Api.Database.Models.Robot", "Robot") + .WithMany() + .HasForeignKey("RobotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InspectionArea"); + b.Navigation("Robot"); }); @@ -1169,9 +1165,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Api.Database.Models.Robot", b => { - b.HasOne("Api.Database.Models.Area", "CurrentArea") + b.HasOne("Api.Database.Models.Deck", "CurrentInspectionArea") .WithMany() - .HasForeignKey("CurrentAreaId"); + .HasForeignKey("CurrentInspectionAreaId"); b.HasOne("Api.Database.Models.Installation", "CurrentInstallation") .WithMany() @@ -1281,7 +1277,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); - b.Navigation("CurrentArea"); + b.Navigation("CurrentInspectionArea"); b.Navigation("CurrentInstallation"); diff --git a/backend/api/Services/AreaService.cs b/backend/api/Services/AreaService.cs index 3c2f5b1a5..20b2b5a14 100644 --- a/backend/api/Services/AreaService.cs +++ b/backend/api/Services/AreaService.cs @@ -168,7 +168,13 @@ private IQueryable GetAreas(bool readOnly = true) .Include(area => area.DefaultLocalizationPose) .Include(area => area.Deck) .ThenInclude(deck => deck != null ? deck.DefaultLocalizationPose : null) + .Include(area => area.Deck) + .ThenInclude(deck => deck.Plant) + .ThenInclude(plant => plant.Installation) + .Include(area => area.Deck) + .ThenInclude(deck => deck.Installation) .Include(area => area.Plant) + .ThenInclude(plant => plant.Installation) .Include(area => area.Installation) .Where((area) => accessibleInstallationCodes.Result.Contains(area.Installation.InstallationCode.ToUpper())); return readOnly ? query.AsNoTracking() : query.AsTracking(); diff --git a/backend/api/Services/DeckService.cs b/backend/api/Services/DeckService.cs index 71f9360ae..b9c8e6e18 100644 --- a/backend/api/Services/DeckService.cs +++ b/backend/api/Services/DeckService.cs @@ -15,7 +15,7 @@ public interface IDeckService public Task> ReadByInstallation(string installationCode, bool readOnly = true); - public Task ReadByName(string deckName, bool readOnly = true); + public Task ReadByInstallationAndName(string installationCode, string deckName, bool readOnly = true); public Task ReadByInstallationAndPlantAndName(Installation installation, Plant plant, string deckName, bool readOnly = true); @@ -65,11 +65,11 @@ public async Task> ReadByInstallation(string installationCode, a.Installation != null && a.Installation.Id.Equals(installation.Id)).ToListAsync(); } - public async Task ReadByName(string deckName, bool readOnly = true) + public async Task ReadByInstallationAndName(string installationCode, string deckName, bool readOnly = true) { if (deckName == null) { return null; } return await GetDecks(readOnly: readOnly).Where(a => - a.Name.ToLower().Equals(deckName.ToLower()) + a.Installation != null && a.Installation.InstallationCode.ToLower().Equals(installationCode.ToLower()) && a.Name.ToLower().Equals(deckName.ToLower()) ).FirstOrDefaultAsync(); } @@ -147,7 +147,7 @@ public async Task Update(Deck deck) private IQueryable GetDecks(bool readOnly = true) { var accessibleInstallationCodes = accessRoleService.GetAllowedInstallationCodes(); - var query = context.Decks.Include(p => p.Plant).Include(i => i.Installation).Include(d => d.DefaultLocalizationPose) + var query = context.Decks.Include(p => p.Plant).ThenInclude(p => p.Installation).Include(i => i.Installation).Include(d => d.DefaultLocalizationPose) .Where((d) => accessibleInstallationCodes.Result.Contains(d.Installation.InstallationCode.ToUpper())); return readOnly ? query.AsNoTracking() : query.AsTracking(); } diff --git a/backend/api/Services/EchoService.cs b/backend/api/Services/EchoService.cs index 9c4abf160..fedeb0f72 100644 --- a/backend/api/Services/EchoService.cs +++ b/backend/api/Services/EchoService.cs @@ -144,7 +144,7 @@ public async Task> GetTasksForMission(string missionSourceId) Source = source, Name = echoMission.Name, InstallationCode = echoMission.InstallationCode, - Area = area + InspectionArea = area.Deck }; return missionDefinition; } diff --git a/backend/api/Services/ErrorHandlingService.cs b/backend/api/Services/ErrorHandlingService.cs index 570075c81..701ac808d 100644 --- a/backend/api/Services/ErrorHandlingService.cs +++ b/backend/api/Services/ErrorHandlingService.cs @@ -18,7 +18,6 @@ public async Task HandleLosingConnectionToIsar(string robotId) await robotService.UpdateRobotStatus(robotId, RobotStatus.Offline); await robotService.UpdateCurrentMissionId(robotId, null); await robotService.UpdateRobotIsarConnected(robotId, false); - await robotService.UpdateCurrentArea(robotId, null); } catch (RobotNotFoundException) { diff --git a/backend/api/Services/Events/MissionEventArgs.cs b/backend/api/Services/Events/MissionEventArgs.cs index 1deba4970..6f72ad3db 100644 --- a/backend/api/Services/Events/MissionEventArgs.cs +++ b/backend/api/Services/Events/MissionEventArgs.cs @@ -11,10 +11,6 @@ public class RobotAvailableEventArgs(string robotId) : EventArgs { public string RobotId { get; } = robotId; } - public class LocalizationMissionSuccessfulEventArgs(string robotId) : EventArgs - { - public string RobotId { get; } = robotId; - } public class RobotEmergencyEventArgs(string robotId, RobotFlotillaStatus? robotFlotillaStatus = null) : EventArgs { diff --git a/backend/api/Services/InspectionFindingService.cs b/backend/api/Services/InspectionFindingService.cs index c36679f31..a6d2fa208 100644 --- a/backend/api/Services/InspectionFindingService.cs +++ b/backend/api/Services/InspectionFindingService.cs @@ -18,11 +18,11 @@ public async Task> RetrieveInspectionFindings(DateTime l var query = readOnly ? context.MissionRuns.AsNoTracking() : context.MissionRuns.AsTracking(); #pragma warning disable CA1304 - return await query.Include(missionRun => missionRun.Area).ThenInclude(area => area != null ? area.Plant : null) + return await query.Include(missionRun => missionRun.InspectionArea).ThenInclude(area => area != null ? area.Plant : null) .Include(missionRun => missionRun.Robot) .Include(missionRun => missionRun.Tasks).ThenInclude(task => task.Inspection) .Where(missionRun => missionRun.Tasks.Any(missionTask => missionTask.Inspection != null && missionTask.Inspection.Id == isarTaskId)) - .Where((m) => m.Area == null || accessibleInstallationCodes.Result.Contains(m.Area.Installation.InstallationCode.ToUpper())) + .Where((m) => m.InspectionArea == null || accessibleInstallationCodes.Result.Contains(m.InspectionArea.Installation.InstallationCode.ToUpper())) .FirstOrDefaultAsync(); #pragma warning restore CA1304 } diff --git a/backend/api/Services/InspectionService.cs b/backend/api/Services/InspectionService.cs index aff7a0e51..c51df67b2 100644 --- a/backend/api/Services/InspectionService.cs +++ b/backend/api/Services/InspectionService.cs @@ -65,11 +65,11 @@ private async Task Update(Inspection inspection) var entry = context.Update(inspection); var missionRun = await context.MissionRuns - .Include(missionRun => missionRun.Area).ThenInclude(area => area != null ? area.Installation : null) + .Include(missionRun => missionRun.InspectionArea).ThenInclude(area => area != null ? area.Installation : null) .Include(missionRun => missionRun.Robot) .Where(missionRun => missionRun.Tasks.Any(missionTask => missionTask.Inspection != null && missionTask.Inspection.Id == inspection.Id)).AsNoTracking() .FirstOrDefaultAsync(); - var installation = missionRun?.Area?.Installation; + var installation = missionRun?.InspectionArea?.Installation; await ApplyDatabaseUpdate(installation); diff --git a/backend/api/Services/IsarService.cs b/backend/api/Services/IsarService.cs index e883917a5..37caef005 100644 --- a/backend/api/Services/IsarService.cs +++ b/backend/api/Services/IsarService.cs @@ -21,15 +21,22 @@ public interface IIsarService public Task GetMediaStreamConfig(Robot robot); } - public class IsarService(IDownstreamApi isarApi, ILogger logger) : IIsarService + public class IsarService(IDownstreamApi isarApi, IMissionDefinitionService missionDefinitionService, ILogger logger) : IIsarService { public const string ServiceName = "IsarApi"; public async Task StartMission(Robot robot, MissionRun missionRun) { - var missionDefinition = new + string? mapName = null; + if (missionRun.MissionId != null) { - mission_definition = new IsarMissionDefinition(missionRun, includeStartPose: missionRun.MissionRunType == MissionRunType.Localization) + var missionDefinition = await missionDefinitionService.ReadById(missionRun.MissionId); + mapName = missionDefinition?.Map?.MapName; + } + + var isarMissionDefinition = new + { + mission_definition = new IsarMissionDefinition(missionRun, mapName: mapName, includeStartPose: true) }; HttpResponseMessage? response; @@ -39,7 +46,7 @@ public async Task StartMission(Robot robot, MissionRun missionRun) HttpMethod.Post, robot.IsarUri, "schedule/start-mission", - missionDefinition + isarMissionDefinition ); } catch (Exception e) diff --git a/backend/api/Services/LocalizationService.cs b/backend/api/Services/LocalizationService.cs index d635eff35..4b9032abf 100644 --- a/backend/api/Services/LocalizationService.cs +++ b/backend/api/Services/LocalizationService.cs @@ -5,11 +5,10 @@ namespace Api.Services public interface ILocalizationService { public Task EnsureRobotIsOnSameInstallationAsMission(Robot robot, MissionDefinition missionDefinition); - public Task RobotIsLocalized(string robotId); - public Task RobotIsOnSameDeckAsMission(string robotId, string areaId); + public Task RobotIsOnSameDeckAsMission(string robotId, string inspectionAreaId); } - public class LocalizationService(ILogger logger, IRobotService robotService, IInstallationService installationService, IAreaService areaService) : ILocalizationService + public class LocalizationService(ILogger logger, IRobotService robotService, IInstallationService installationService, IDeckService deckService) : ILocalizationService { public async Task EnsureRobotIsOnSameInstallationAsMission(Robot robot, MissionDefinition missionDefinition) @@ -31,21 +30,7 @@ public async Task EnsureRobotIsOnSameInstallationAsMission(Robot robot, MissionD } } - public async Task RobotIsLocalized(string robotId) - { - var robot = await robotService.ReadById(robotId, readOnly: true); - if (robot is null) - { - string errorMessage = $"Robot with ID: {robotId} was not found in the database"; - logger.LogError("{Message}", errorMessage); - throw new RobotNotFoundException(errorMessage); - } - - if (robot.RobotCapabilities is not null && robot.RobotCapabilities.Contains(RobotCapabilitiesEnum.auto_localize)) { return true; } - return robot.CurrentArea is not null; - } - - public async Task RobotIsOnSameDeckAsMission(string robotId, string areaId) + public async Task RobotIsOnSameDeckAsMission(string robotId, string inspectionAreaId) { var robot = await robotService.ReadById(robotId, readOnly: true); if (robot is null) @@ -57,35 +42,29 @@ public async Task RobotIsOnSameDeckAsMission(string robotId, string areaId if (robot.RobotCapabilities is not null && robot.RobotCapabilities.Contains(RobotCapabilitiesEnum.auto_localize)) { return true; } - if (robot.CurrentArea is null) + if (robot.CurrentInspectionArea is null) { - const string ErrorMessage = "The robot is not associated with an area and a mission may not be started"; + const string ErrorMessage = "The robot is not associated with an inspection area and a mission may not be started"; logger.LogError("{Message}", ErrorMessage); throw new RobotCurrentAreaMissingException(ErrorMessage); } - var missionArea = await areaService.ReadById(areaId, readOnly: true); - if (missionArea is null) + var missionInspectionArea = await deckService.ReadById(inspectionAreaId, readOnly: true); + if (missionInspectionArea is null) { - const string ErrorMessage = "The robot is not located on the same deck as the mission as the area has not been set"; + const string ErrorMessage = "The mission does not have an associated inspection area"; logger.LogError("{Message}", ErrorMessage); - throw new AreaNotFoundException(ErrorMessage); + throw new DeckNotFoundException(ErrorMessage); } - if (robot.CurrentArea?.Deck is null) + if (robot.CurrentInspectionArea is null) { const string ErrorMessage = "The robot area is not associated with any deck"; logger.LogError("{Message}", ErrorMessage); throw new DeckNotFoundException(ErrorMessage); } - if (missionArea.Deck is null) - { - const string ErrorMessage = "The mission area is not associated with any deck"; - logger.LogError("{Message}", ErrorMessage); - throw new DeckNotFoundException(ErrorMessage); - } - return robot.CurrentArea.Deck.Id == missionArea.Deck.Id; + return robot.CurrentInspectionArea.Id == missionInspectionArea.Id; } } } diff --git a/backend/api/Services/MapService.cs b/backend/api/Services/MapService.cs index 7c7e424de..eab825049 100644 --- a/backend/api/Services/MapService.cs +++ b/backend/api/Services/MapService.cs @@ -10,7 +10,7 @@ public interface IMapService { public Task FetchMapImage(string mapName, string installationCode); public Task ChooseMapFromPositions(IList positions, string installationCode); - public Task AssignMapToMission(MissionRun mission); + public Task ChooseMapFromMissionRunTasks(MissionRun mission); } public class MapService(ILogger logger, @@ -57,7 +57,7 @@ public async Task FetchMapImage(string mapName, string installationCode) return map; } - public async Task AssignMapToMission(MissionRun missionRun) + public async Task ChooseMapFromMissionRunTasks(MissionRun missionRun) { MapMetadata? mapMetadata; var positions = new List(); @@ -81,16 +81,16 @@ public async Task AssignMapToMission(MissionRun missionRun) catch (ArgumentOutOfRangeException) { logger.LogWarning("Unable to find a map for mission '{missionId}'", missionRun.Id); - return; + return null; } if (mapMetadata == null) { - return; + return null; } - missionRun.Map = mapMetadata; logger.LogInformation("Assigned map {map} to mission {mission}", mapMetadata.MapName, missionRun.Name); + return mapMetadata; } private Boundary ExtractMapMetadata(BlobItem map) diff --git a/backend/api/Services/MissionDefinitionService.cs b/backend/api/Services/MissionDefinitionService.cs index 3027c5295..1a4a2e113 100644 --- a/backend/api/Services/MissionDefinitionService.cs +++ b/backend/api/Services/MissionDefinitionService.cs @@ -17,9 +17,7 @@ public interface IMissionDefinitionService public Task> ReadAll(MissionDefinitionQueryStringParameters parameters, bool readOnly = true); - public Task> ReadByAreaId(string areaId, bool readOnly = true); - - public Task> ReadByDeckId(string deckId, bool readOnly = true); + public Task> ReadByInspectionAreaId(string inspectionAreaId, bool readOnly = true); public Task?> GetTasksFromSource(Source source); @@ -55,12 +53,12 @@ public class MissionDefinitionService(FlotillaDbContext context, public async Task Create(MissionDefinition missionDefinition) { if (missionDefinition.LastSuccessfulRun is not null) { context.Entry(missionDefinition.LastSuccessfulRun).State = EntityState.Unchanged; } - if (missionDefinition.Area is not null) { context.Entry(missionDefinition.Area).State = EntityState.Unchanged; } + if (missionDefinition.InspectionArea is not null) { context.Entry(missionDefinition.InspectionArea).State = EntityState.Unchanged; } if (missionDefinition.Source is not null) { context.Entry(missionDefinition.Source).State = EntityState.Unchanged; } await context.MissionDefinitions.AddAsync(missionDefinition); - await ApplyDatabaseUpdate(missionDefinition.Area?.Installation); - _ = signalRService.SendMessageAsync("Mission definition created", missionDefinition.Area?.Installation, new MissionDefinitionResponse(missionDefinition)); + await ApplyDatabaseUpdate(missionDefinition.InspectionArea?.Installation); + _ = signalRService.SendMessageAsync("Mission definition created", missionDefinition.InspectionArea?.Installation, new MissionDefinitionResponse(missionDefinition)); DetachTracking(missionDefinition); return missionDefinition; } @@ -89,10 +87,10 @@ public async Task> ReadAll(MissionDefinitionQuerySt ); } - public async Task> ReadByAreaId(string areaId, bool readOnly = true) + public async Task> ReadByInspectionAreaId(string inspectionAreaId, bool readOnly = true) { return await GetMissionDefinitionsWithSubModels(readOnly: readOnly).Where( - m => m.IsDeprecated == false && m.Area != null && m.Area.Id == areaId).ToListAsync(); + m => m.IsDeprecated == false && m.InspectionArea != null && m.InspectionArea.Id == inspectionAreaId).ToListAsync(); } public async Task> ReadBySourceId(string sourceId, bool readOnly = true) @@ -101,12 +99,6 @@ public async Task> ReadBySourceId(string sourceId, bool m => m.IsDeprecated == false && m.Source.SourceId != null && m.Source.SourceId == sourceId).ToListAsync(); } - public async Task> ReadByDeckId(string deckId, bool readOnly = true) - { - return await GetMissionDefinitionsWithSubModels(readOnly: readOnly).Where( - m => m.IsDeprecated == false && m.Area != null && m.Area.Deck != null && m.Area.Deck.Id == deckId).ToListAsync(); - } - public async Task UpdateLastSuccessfulMissionRun(string missionRunId, string missionDefinitionId) { var missionRun = await missionRunService.ReadById(missionRunId, readOnly: true); @@ -134,11 +126,11 @@ public async Task UpdateLastSuccessfulMissionRun(string missi public async Task Update(MissionDefinition missionDefinition) { if (missionDefinition.LastSuccessfulRun is not null) { context.Entry(missionDefinition.LastSuccessfulRun).State = EntityState.Unchanged; } - if (missionDefinition.Area is not null) { context.Entry(missionDefinition.Area).State = EntityState.Unchanged; } + if (missionDefinition.InspectionArea is not null) { context.Entry(missionDefinition.InspectionArea).State = EntityState.Unchanged; } var entry = context.Update(missionDefinition); - await ApplyDatabaseUpdate(missionDefinition.Area?.Installation); - _ = signalRService.SendMessageAsync("Mission definition updated", missionDefinition?.Area?.Installation, missionDefinition != null ? new MissionDefinitionResponse(missionDefinition) : null); + await ApplyDatabaseUpdate(missionDefinition.InspectionArea?.Installation); + _ = signalRService.SendMessageAsync("Mission definition updated", missionDefinition?.InspectionArea?.Installation, missionDefinition != null ? new MissionDefinitionResponse(missionDefinition) : null); return entry.Entity; } @@ -149,7 +141,7 @@ public async Task Update(MissionDefinition missionDefinition) if (missionDefinition is null) { return null; } missionDefinition.IsDeprecated = true; - await ApplyDatabaseUpdate(missionDefinition.Area?.Installation); + await ApplyDatabaseUpdate(missionDefinition.InspectionArea?.Installation); return missionDefinition; } @@ -173,24 +165,20 @@ private IQueryable GetMissionDefinitionsWithSubModels(bool re { var accessibleInstallationCodes = accessRoleService.GetAllowedInstallationCodes(); var query = context.MissionDefinitions - .Include(missionDefinition => missionDefinition.Area != null ? missionDefinition.Area.Deck : null) - .ThenInclude(deck => deck != null ? deck.Plant : null) - .ThenInclude(plant => plant != null ? plant.Installation : null) - .Include(missionDefinition => missionDefinition.Area) - .ThenInclude(area => area != null ? area.Deck : null) - .Include(missionDefinition => missionDefinition.Area) - .ThenInclude(area => area != null ? area.Plant : null) - .Include(missionDefinition => missionDefinition.Area) - .ThenInclude(area => area != null ? area.Installation : null) + .Include(missionDefinition => missionDefinition.InspectionArea) + .ThenInclude(deck => deck!.Plant) + .ThenInclude(plant => plant.Installation) + .Include(missionDefinition => missionDefinition.InspectionArea) + .ThenInclude(area => area!.Installation) .Include(missionDefinition => missionDefinition.Source) .Include(missionDefinition => missionDefinition.LastSuccessfulRun) .ThenInclude(missionRun => missionRun != null ? missionRun.Tasks : null)! .ThenInclude(missionTask => missionTask.Inspection) .ThenInclude(inspection => inspection != null ? inspection.InspectionFindings : null) - .Include(missionDefinition => missionDefinition.Area != null ? missionDefinition.Area.Deck : null) - .ThenInclude(deck => deck != null ? deck.DefaultLocalizationPose : null) + .Include(missionDefinition => missionDefinition.InspectionArea) + .ThenInclude(deck => deck!.DefaultLocalizationPose) .ThenInclude(defaultLocalizationPose => defaultLocalizationPose != null ? defaultLocalizationPose.Pose : null) - .Where((m) => m.Area == null || accessibleInstallationCodes.Result.Contains(m.Area.Installation.InstallationCode.ToUpper())); + .Where((m) => m.InspectionArea == null || accessibleInstallationCodes.Result.Contains(m.InspectionArea.Installation.InstallationCode.ToUpper())); return readOnly ? query.AsNoTracking() : query.AsTracking(); } @@ -209,7 +197,7 @@ private static void SearchByName(ref IQueryable missionDefini /// /// Filters by - /// and + /// and /// and /// /// Uses LINQ Expression trees (see @@ -221,10 +209,10 @@ private static Expression> ConstructFilter( MissionDefinitionQueryStringParameters parameters ) { - Expression> areaFilter = parameters.Area is null + Expression> inspectionAreaFilter = parameters.InspectionArea is null ? missionDefinition => true : missionDefinition => - missionDefinition.Area != null && missionDefinition.Area.Name.ToLower().Equals(parameters.Area.Trim().ToLower()); + missionDefinition.InspectionArea != null && missionDefinition.InspectionArea.Name.ToLower().Equals(parameters.InspectionArea.Trim().ToLower()); Expression> installationFilter = parameters.InstallationCode is null ? missionDefinition => true @@ -237,7 +225,7 @@ MissionDefinitionQueryStringParameters parameters // Combining the body of the filters to create the combined filter, using invoke to force parameter substitution Expression body = Expression.AndAlso( Expression.Invoke(installationFilter, missionDefinitionExpression), - Expression.Invoke(areaFilter, missionDefinitionExpression) + Expression.Invoke(inspectionAreaFilter, missionDefinitionExpression) ); // Constructing the resulting lambda expression by combining parameter and body diff --git a/backend/api/Services/MissionRunService.cs b/backend/api/Services/MissionRunService.cs index 11227d5ba..127f075cd 100644 --- a/backend/api/Services/MissionRunService.cs +++ b/backend/api/Services/MissionRunService.cs @@ -29,18 +29,10 @@ public interface IMissionRunService public Task ReadNextScheduledEmergencyMissionRun(string robotId, bool readOnly = true); - public Task ReadNextScheduledLocalizationMissionRun(string robotId, bool readOnly = true); - public Task> ReadMissionRuns(string robotId, MissionRunType? missionRunType, IList? filterStatuses = null, bool readOnly = true); public Task ReadLastExecutedMissionRunByRobot(string robotId, bool readOnly = true); - public Task PendingLocalizationMissionRunExists(string robotId); - - public Task OngoingOrPausedLocalizationMissionRunExists(string robotId); - - public Task PendingOrOngoingLocalizationMissionRunExists(string robotId); - public Task PendingOrOngoingReturnToHomeMissionRunExists(string robotId); public bool IncludesUnsupportedInspectionType(MissionRun missionRun); @@ -81,7 +73,7 @@ public class MissionRunService( ILogger logger, IAccessRoleService accessRoleService, IMissionTaskService missionTaskService, - IAreaService areaService, + IDeckService deckService, IRobotService robotService, IUserInfoService userInfoService) : IMissionRunService { @@ -94,12 +86,12 @@ public async Task Create(MissionRun missionRun, bool triggerCreatedM throw new UnsupportedRobotCapabilityException($"Mission {missionRun.Name} contains inspection types not supported by robot: {missionRun.Robot.Name}."); } - if (missionRun.Area is not null) { context.Entry(missionRun.Area).State = EntityState.Unchanged; } + if (missionRun.InspectionArea is not null) { context.Entry(missionRun.InspectionArea).State = EntityState.Unchanged; } if (missionRun.Robot is not null) { context.Entry(missionRun.Robot).State = EntityState.Unchanged; } await context.MissionRuns.AddAsync(missionRun); - await ApplyDatabaseUpdate(missionRun.Area?.Installation); + await ApplyDatabaseUpdate(missionRun.InspectionArea?.Installation); - _ = signalRService.SendMessageAsync("Mission run created", missionRun.Area?.Installation, new MissionRunResponse(missionRun)); + _ = signalRService.SendMessageAsync("Mission run created", missionRun.InspectionArea?.Installation, new MissionRunResponse(missionRun)); if (triggerCreatedMissionRunEvent) { @@ -164,13 +156,6 @@ public async Task> ReadMissionRunQueue(string robotId, bool re missionRun.Robot.Id == robotId && missionRun.MissionRunType == MissionRunType.Emergency && missionRun.Status == MissionStatus.Pending); } - public async Task ReadNextScheduledLocalizationMissionRun(string robotId, bool readOnly = true) - { - return await GetMissionRunsWithSubModels(readOnly: readOnly) - .OrderBy(missionRun => missionRun.DesiredStartTime) - .FirstOrDefaultAsync(missionRun => missionRun.Robot.Id == robotId && missionRun.Status == MissionStatus.Pending && missionRun.MissionRunType == MissionRunType.Localization); - } - public async Task> ReadMissionRuns(string robotId, MissionRunType? missionRunType, IList? filterStatuses = null, bool readOnly = true) { var missionFilter = ConstructFilter(new MissionRunQueryStringParameters @@ -204,33 +189,6 @@ public async Task> ReadMissionRuns(string robotId, MissionRunT .FirstOrDefaultAsync(); } - public async Task PendingLocalizationMissionRunExists(string robotId) - { - var pendingMissionRuns = await ReadMissionRunQueue(robotId, readOnly: true); - return pendingMissionRuns.Any((m) => m.IsLocalizationMission()); - } - - public async Task OngoingOrPausedLocalizationMissionRunExists(string robotId) - { - var ongoingMissionRuns = await GetMissionRunsWithSubModels(readOnly: true) - .Where(missionRun => missionRun.Robot.Id == robotId && (missionRun.Status == MissionStatus.Ongoing || missionRun.Status == MissionStatus.Paused)) - .OrderBy(missionRun => missionRun.DesiredStartTime) - .ToListAsync(); - return ongoingMissionRuns.Any((m) => m.IsLocalizationMission()); - } - - public async Task PendingOrOngoingLocalizationMissionRunExists(string robotId) - { - var pendingMissionRuns = await ReadMissionRunQueue(robotId, readOnly: true); - if (pendingMissionRuns.Any((m) => m.IsLocalizationMission())) return true; - - var ongoingMissionRuns = await GetMissionRunsWithSubModels(readOnly: true) - .Where(missionRun => missionRun.Robot.Id == robotId && missionRun.Status == MissionStatus.Ongoing) - .OrderBy(missionRun => missionRun.DesiredStartTime) - .ToListAsync(); - return ongoingMissionRuns.Any((m) => m.IsLocalizationMission()); - } - public async Task PendingOrOngoingReturnToHomeMissionRunExists(string robotId) { var pendingMissionRuns = await ReadMissionRunQueue(robotId, readOnly: true); @@ -253,11 +211,11 @@ public bool IncludesUnsupportedInspectionType(MissionRun missionRun) public async Task Update(MissionRun missionRun) { context.Entry(missionRun.Robot).State = EntityState.Unchanged; - if (missionRun.Area is not null) { context.Entry(missionRun.Area).State = EntityState.Unchanged; } + if (missionRun.InspectionArea is not null) { context.Entry(missionRun.InspectionArea).State = EntityState.Unchanged; } var entry = context.Update(missionRun); - await ApplyDatabaseUpdate(missionRun.Area?.Installation); - _ = signalRService.SendMessageAsync("Mission run updated", missionRun?.Area?.Installation, missionRun != null ? new MissionRunResponse(missionRun) : null); + await ApplyDatabaseUpdate(missionRun.InspectionArea?.Installation); + _ = signalRService.SendMessageAsync("Mission run updated", missionRun?.InspectionArea?.Installation, missionRun != null ? new MissionRunResponse(missionRun) : null); DetachTracking(missionRun!); return entry.Entity; } @@ -272,7 +230,7 @@ public async Task Update(MissionRun missionRun) } await UpdateMissionRunProperty(missionRun.Id, "IsDeprecated", true); - _ = signalRService.SendMessageAsync("Mission run deleted", missionRun?.Area?.Installation, missionRun != null ? new MissionRunResponse(missionRun) : null); + _ = signalRService.SendMessageAsync("Mission run deleted", missionRun?.InspectionArea?.Installation, missionRun != null ? new MissionRunResponse(missionRun) : null); return missionRun; } @@ -281,28 +239,33 @@ private IQueryable GetMissionRunsWithSubModels(bool readOnly = true) { var accessibleInstallationCodes = accessRoleService.GetAllowedInstallationCodes(); var query = context.MissionRuns - .Include(missionRun => missionRun.Area) - .ThenInclude(area => area != null ? area.Deck : null) + .Include(missionRun => missionRun.InspectionArea) .ThenInclude(deck => deck != null ? deck.Plant : null) .ThenInclude(plant => plant != null ? plant.Installation : null) - .Include(missionRun => missionRun.Area) + .Include(missionRun => missionRun.InspectionArea) .ThenInclude(area => area != null ? area.Plant : null) .ThenInclude(plant => plant != null ? plant.Installation : null) - .Include(missionRun => missionRun.Area) + .Include(missionRun => missionRun.InspectionArea) .ThenInclude(area => area != null ? area.Installation : null) - .Include(missionRun => missionRun.Area) - .ThenInclude(area => area != null ? area.Deck : null) + .Include(missionRun => missionRun.InspectionArea) .ThenInclude(deck => deck != null ? deck.DefaultLocalizationPose : null) .ThenInclude(defaultLocalizationPose => defaultLocalizationPose != null ? defaultLocalizationPose.Pose : null) .Include(missionRun => missionRun.Robot) .Include(missionRun => missionRun.Robot) + .ThenInclude(robot => robot.CurrentInspectionArea) + .ThenInclude(a => a != null ? a.Installation : null) + .Include(missionRun => missionRun.Robot) + .ThenInclude(robot => robot.CurrentInspectionArea) + .ThenInclude(a => a != null ? a.Plant : null) + .ThenInclude(p => p != null ? p.Installation : null) + .Include(missionRun => missionRun.Robot) .ThenInclude(robot => robot.Model) .Include(missionRun => missionRun.Tasks) .ThenInclude(task => task.Inspection) .ThenInclude(inspections => inspections != null ? inspections.InspectionFindings : null) .Include(missionRun => missionRun.Robot) .ThenInclude(robot => robot.CurrentInstallation) - .Where((m) => m.Area == null || accessibleInstallationCodes.Result.Contains(m.Area.Installation.InstallationCode.ToUpper())) + .Where((m) => m.InspectionArea == null || accessibleInstallationCodes.Result.Contains(m.InspectionArea.Installation.InstallationCode.ToUpper())) .Where((m) => m.IsDeprecated == false); return readOnly ? query.AsNoTracking() : query.AsTracking(); } @@ -367,7 +330,7 @@ private static void SearchByTag(ref IQueryable missionRuns, string? /// /// Filters by , - /// , + /// , /// , /// , /// , @@ -375,7 +338,6 @@ private static void SearchByTag(ref IQueryable missionRuns, string? /// , /// , /// , - /// , /// , /// , /// , @@ -393,11 +355,11 @@ private static Expression> ConstructFilter( MissionRunQueryStringParameters parameters ) { - Expression> areaFilter = parameters.Area is null + Expression> inspectionAreaFilter = parameters.InspectionArea is null ? missionRun => true : missionRun => - missionRun.Area != null && - missionRun.Area.Name.ToLower().Equals(parameters.Area.Trim().ToLower()); + missionRun.InspectionArea != null && + missionRun.InspectionArea.Name.ToLower().Equals(parameters.InspectionArea.Trim().ToLower()); Expression> installationFilter = parameters.InstallationCode is null ? missionRun => true @@ -430,10 +392,6 @@ MissionRunQueryStringParameters parameters task => task.Inspection != null && parameters.InspectionTypes.Contains(task.Inspection.InspectionType) ); - Expression> localizationFilter = !parameters.ExcludeLocalization - ? missionRun => true - : missionRun => !(missionRun.Tasks.Count() == 1 && missionRun.Tasks.All(task => task.Type == MissionTaskType.Localization)); - Expression> returnTohomeFilter = !parameters.ExcludeReturnToHome ? missionRun => true : missionRun => !(missionRun.Tasks.Count() == 1 && missionRun.Tasks.All(task => task.Type == MissionTaskType.ReturnHome)); @@ -475,16 +433,16 @@ MissionRunQueryStringParameters parameters Expression.AndAlso( Expression.Invoke(inspectionTypeFilter, missionRun), Expression.AndAlso( - Expression.Invoke(localizationFilter, missionRun), + Expression.Invoke(returnTohomeFilter, missionRun), Expression.AndAlso( - Expression.Invoke(returnTohomeFilter, missionRun), + Expression.Invoke(desiredStartTimeFilter, missionRun), Expression.AndAlso( - Expression.Invoke(desiredStartTimeFilter, missionRun), + Expression.Invoke(startTimeFilter, missionRun), Expression.AndAlso( - Expression.Invoke(startTimeFilter, missionRun), + Expression.Invoke(endTimeFilter, missionRun), Expression.AndAlso( - Expression.Invoke(endTimeFilter, missionRun), - Expression.Invoke(robotTypeFilter, missionRun) + Expression.Invoke(robotTypeFilter, missionRun), + Expression.Invoke(inspectionAreaFilter, missionRun) ) ) ) @@ -540,7 +498,7 @@ public async Task UpdateMissionRunStatusByIsarMissionId(string isarM missionRun = await UpdateMissionRunProperty(missionRun.Id, "MissionStatus", missionStatus); - if (missionRun.Status == MissionStatus.Failed) { _ = signalRService.SendMessageAsync("Mission run failed", missionRun?.Area?.Installation, missionRun != null ? new MissionRunResponse(missionRun) : null); } + if (missionRun.Status == MissionStatus.Failed) { _ = signalRService.SendMessageAsync("Mission run failed", missionRun?.InspectionArea?.Installation, missionRun != null ? new MissionRunResponse(missionRun) : null); } return missionRun!; } @@ -608,7 +566,7 @@ public void DetachTracking(MissionRun missionRun) { missionTaskService.DetachTracking(task); } - if (missionRun.Area != null) areaService.DetachTracking(missionRun.Area); + if (missionRun.InspectionArea != null) deckService.DetachTracking(missionRun.InspectionArea); if (missionRun.Robot != null) robotService.DetachTracking(missionRun.Robot); context.Entry(missionRun).State = EntityState.Detached; } diff --git a/backend/api/Services/MissionSchedulingService.cs b/backend/api/Services/MissionSchedulingService.cs index 3702e2eb7..203862386 100644 --- a/backend/api/Services/MissionSchedulingService.cs +++ b/backend/api/Services/MissionSchedulingService.cs @@ -8,7 +8,7 @@ namespace Api.Services { public interface IMissionSchedulingService { - public Task StartNextMissionRunIfSystemIsAvailable(string robotId); + public Task StartNextMissionRunIfSystemIsAvailable(Robot robot); public Task OngoingMission(string robotId); @@ -18,7 +18,7 @@ public interface IMissionSchedulingService public Task AbortAllScheduledMissions(string robotId, string? abortReason = null); - public Task ScheduleMissionToDriveToDockPosition(string robotId, string areaId); + public Task ScheduleMissionToDriveToDockPosition(string robotId); public Task UnfreezeMissionRunQueueForRobot(string robotId); @@ -26,32 +26,22 @@ public interface IMissionSchedulingService public void TriggerRobotAvailable(RobotAvailableEventArgs e); - public void TriggerLocalizationMissionSuccessful(LocalizationMissionSuccessfulEventArgs e); - public Task AbortActiveReturnToHomeMission(string robotId); } public class MissionSchedulingService(ILogger logger, IMissionRunService missionRunService, IRobotService robotService, - IAreaService areaService, IIsarService isarService, ILocalizationService localizationService, IReturnToHomeService returnToHomeService, ISignalRService signalRService, IErrorHandlingService errorHandlingService) : IMissionSchedulingService + IIsarService isarService, ILocalizationService localizationService, IReturnToHomeService returnToHomeService, ISignalRService signalRService, IErrorHandlingService errorHandlingService) : IMissionSchedulingService { - public async Task StartNextMissionRunIfSystemIsAvailable(string robotId) + public async Task StartNextMissionRunIfSystemIsAvailable(Robot robot) { - logger.LogInformation("Starting next mission run if system is available for robot ID: {RobotId}", robotId); - var robot = await robotService.ReadById(robotId, readOnly: true); - if (robot == null) - { - logger.LogError("Robot with ID: {RobotId} was not found in the database", robotId); - return; - } - - logger.LogInformation("Robot {robotName} has status {robotStatus} and current area {areaName}", robot.Name, robot.Status, robot.CurrentArea?.Name); + logger.LogInformation("Robot {robotName} has status {robotStatus} and current area {areaName}", robot.Name, robot.Status, robot.CurrentInspectionArea?.Name); MissionRun? missionRun; try { missionRun = await SelectNextMissionRun(robot.Id); } catch (RobotNotFoundException) { - logger.LogError("Robot with ID: {RobotId} was not found in the database", robotId); + logger.LogError("Robot with ID: {RobotId} was not found in the database", robot.Id); return; } @@ -65,62 +55,30 @@ public async Task StartNextMissionRunIfSystemIsAvailable(string robotId) { logger.LogInformation("The robot was ready to start mission, but no mission is scheduled"); - if (!await localizationService.RobotIsLocalized(robotId)) - { - string infoMessage = $"Not scheduling a return to home mission as the robot {robotId} is not localized."; - logger.LogInformation("{Message}", infoMessage); - return; - } - - if (robot.RobotCapabilities == null || !robot.RobotCapabilities.Contains(RobotCapabilitiesEnum.return_to_home)) - { - await robotService.UpdateCurrentArea(robot.Id, null); - return; - } - else + if (robot.RobotCapabilities != null && robot.RobotCapabilities.Contains(RobotCapabilitiesEnum.return_to_home)) { try { missionRun = await returnToHomeService.ScheduleReturnToHomeMissionRunIfNotAlreadyScheduledOrRobotIsHome(robot.Id); } catch (ReturnToHomeMissionFailedToScheduleException) { signalRService.ReportGeneralFailToSignalR(robot, $"Failed to schedule return to home for robot {robot.Name}", ""); logger.LogError("Failed to schedule a return to home mission for robot {RobotId}", robot.Id); - await robotService.UpdateCurrentArea(robot.Id, null); - } - - if (missionRun == null) { return; } // The robot is already home - - var postReturnToHomeMissionCreatedRobot = await robotService.ReadById(missionRun.Robot.Id, readOnly: true); - if (postReturnToHomeMissionCreatedRobot == null) - { - logger.LogInformation("Could not find robot {Name}", missionRun.Robot.Name); - return; } - - logger.LogInformation( - "Post return to home mission created: Robot {robotName} has status {robotStatus} and current area {areaName}", - postReturnToHomeMissionCreatedRobot.Name, - postReturnToHomeMissionCreatedRobot.Status, - postReturnToHomeMissionCreatedRobot.CurrentArea?.Name - ); } } + if (missionRun == null) { return; } + if (!await TheSystemIsAvailableToRunAMission(robot.Id, missionRun.Id)) { logger.LogInformation("Mission {MissionRunId} was put on the queue as the system may not start a mission now", missionRun.Id); return; } - // Verify that localization is fine - if (!await localizationService.RobotIsLocalized(robot.Id) && !missionRun.IsLocalizationMission()) + if (robot.CurrentInspectionArea == null && missionRun.InspectionArea != null) { - logger.LogError("Tried to schedule mission {MissionRunId} on robot {RobotId} before the robot was localized, scheduled missions will be aborted", missionRun.Id, robot.Id); - try { await AbortAllScheduledMissions(robot.Id, "Aborted: Robot was not localized"); } - catch (RobotNotFoundException) { logger.LogError("Failed to abort scheduled missions for robot {RobotId}", robot.Id); } - return; + await robotService.UpdateCurrentInspectionArea(robot.Id, missionRun.InspectionArea.Id); } - - if (!missionRun.IsLocalizationMission() && !await localizationService.RobotIsOnSameDeckAsMission(robot.Id, missionRun.Area.Id)) + else if (!await localizationService.RobotIsOnSameDeckAsMission(robot.Id, missionRun.InspectionArea!.Id)) { logger.LogError("Robot {RobotId} is not on the same deck as the mission run {MissionRunId}. Aborting all mission runs", robot.Id, missionRun.Id); try { await AbortAllScheduledMissions(robot.Id, "Aborted: Robot was at different deck"); } @@ -130,7 +88,6 @@ public async Task StartNextMissionRunIfSystemIsAvailable(string robotId) catch (ReturnToHomeMissionFailedToScheduleException) { logger.LogError("Failed to schedule a return to home mission for robot {RobotId}", robot.Id); - await robotService.UpdateCurrentArea(robot.Id, null); } return; } @@ -180,7 +137,6 @@ or MissionRunNotFoundException catch (ReturnToHomeMissionFailedToScheduleException) { logger.LogError("Failed to schedule a return to home mission for robot {RobotId}", robot.Id); - await robotService.UpdateCurrentArea(robot.Id, null); return null; } return missionRun; @@ -280,15 +236,8 @@ public async Task AbortAllScheduledMissions(string robotId, string? abortReason) } } - public async Task ScheduleMissionToDriveToDockPosition(string robotId, string areaId) + public async Task ScheduleMissionToDriveToDockPosition(string robotId) { - var area = await areaService.ReadById(areaId, readOnly: true); - if (area == null) - { - logger.LogError("Could not find area with ID {AreaId}", areaId); - return; - } - var robot = await robotService.ReadById(robotId, readOnly: true); if (robot == null) { @@ -296,14 +245,28 @@ public async Task ScheduleMissionToDriveToDockPosition(string robotId, string ar return; } - if (robot.CurrentArea?.Deck.DefaultLocalizationPose == null) + Pose robotPose; + + if (robot.CurrentInspectionArea != null) + { + if (robot.CurrentInspectionArea?.DefaultLocalizationPose == null) + { + robotPose = new Pose(); + } + else + { + robotPose = robot.CurrentInspectionArea.DefaultLocalizationPose.Pose; + } + } + else { - throw new DockException($"Robot with ID: {robotId} has no available Dock or localization poses in its current area"); + string errorMessage = $"Robot with ID {robotId} could return home as it did not have an inspection area"; + logger.LogError("{Message}", errorMessage); + throw new DeckNotFoundException(errorMessage); } - var closestDockPosition = robot.CurrentArea.Deck.DefaultLocalizationPose.Pose; // Cloning to avoid tracking same object - var clonedPose = ObjectCopier.Clone(closestDockPosition); + var clonedPose = ObjectCopier.Clone(robotPose); var customTaskQuery = new CustomTaskQuery { RobotPose = clonedPose, @@ -315,15 +278,14 @@ public async Task ScheduleMissionToDriveToDockPosition(string robotId, string ar Name = "Drive to Docking Station", Robot = robot, MissionRunType = MissionRunType.Emergency, - InstallationCode = area.Installation.InstallationCode, - Area = area, + InstallationCode = robot.CurrentInstallation.InstallationCode, + InspectionArea = robot.CurrentInspectionArea!, Status = MissionStatus.Pending, DesiredStartTime = DateTime.UtcNow, Tasks = new List( [ new MissionTask(customTaskQuery) - ]), - Map = new MapMetadata() + ]) }; try @@ -346,11 +308,6 @@ public void TriggerRobotAvailable(RobotAvailableEventArgs e) OnRobotAvailable(e); } - public void TriggerLocalizationMissionSuccessful(LocalizationMissionSuccessfulEventArgs e) - { - OnLocalizationMissionSuccessful(e); - } - private async Task SelectNextMissionRun(string robotId) { var robot = await robotService.ReadById(robotId, readOnly: true); @@ -361,7 +318,7 @@ public void TriggerLocalizationMissionSuccessful(LocalizationMissionSuccessfulEv throw new RobotNotFoundException(errorMessage); } - var missionRun = await missionRunService.ReadNextScheduledLocalizationMissionRun(robot.Id, readOnly: true) ?? await missionRunService.ReadNextScheduledEmergencyMissionRun(robot.Id, readOnly: true); + var missionRun = await missionRunService.ReadNextScheduledEmergencyMissionRun(robot.Id, readOnly: true); if (robot.MissionQueueFrozen == false && missionRun == null) { missionRun = await missionRunService.ReadNextScheduledMissionRun(robot.Id, readOnly: true); } return missionRun; } @@ -396,12 +353,11 @@ private async Task MoveInterruptedMissionsToQueue(IEnumerable interrupte Name = missionRun.Name, Robot = missionRun.Robot, MissionRunType = missionRun.MissionRunType, - InstallationCode = missionRun.Area!.Installation.InstallationCode, - Area = missionRun.Area, + InstallationCode = missionRun.InspectionArea!.Installation.InstallationCode, + InspectionArea = missionRun.InspectionArea, Status = MissionStatus.Pending, DesiredStartTime = DateTime.UtcNow, - Tasks = unfinishedTasks, - Map = new MapMetadata() + Tasks = unfinishedTasks }; try @@ -522,7 +478,7 @@ private async Task TheSystemIsAvailableToRunAMission(string robotId, strin throw new MissionRunNotFoundException(errorMessage); } - if (robot.MissionQueueFrozen && missionRun.MissionRunType != MissionRunType.Emergency && missionRun.MissionRunType != MissionRunType.Localization) + if (robot.MissionQueueFrozen && missionRun.MissionRunType != MissionRunType.Emergency) { logger.LogInformation("Mission run {MissionRunId} was not started as the mission run queue for robot {RobotName} is frozen", missionRun.Id, robot.Name); return false; @@ -543,11 +499,6 @@ private async Task TheSystemIsAvailableToRunAMission(string robotId, strin logger.LogWarning("Mission run {MissionRunId} was not started as the robot {RobotId} is deprecated", missionRun.Id, robot.Id); return false; } - if (await missionRunService.OngoingOrPausedLocalizationMissionRunExists(robot.Id)) - { - logger.LogInformation("Mission run {MissionRunId} was not started as there is an ongoing localization mission", missionRun.Id); - return false; - } return true; } @@ -571,16 +522,7 @@ public async Task AbortActiveReturnToHomeMission(string robotId) catch (MissionRunNotFoundException) { return; } } - private static float CalculateDistance(Pose pose1, Pose pose2) - { - var pos1 = pose1.Position; - var pos2 = pose2.Position; - return (float)Math.Sqrt(Math.Pow(pos1.X - pos2.X, 2) + Math.Pow(pos1.Y - pos2.Y, 2) + Math.Pow(pos1.Z - pos2.Z, 2)); - } - protected virtual void OnRobotAvailable(RobotAvailableEventArgs e) { RobotAvailable?.Invoke(this, e); } public static event EventHandler? RobotAvailable; - protected virtual void OnLocalizationMissionSuccessful(LocalizationMissionSuccessfulEventArgs e) { LocalizationMissionSuccessful?.Invoke(this, e); } - public static event EventHandler? LocalizationMissionSuccessful; } } diff --git a/backend/api/Services/Models/IsarMissionDefinition.cs b/backend/api/Services/Models/IsarMissionDefinition.cs index e6aa5f4be..ed22db36e 100644 --- a/backend/api/Services/Models/IsarMissionDefinition.cs +++ b/backend/api/Services/Models/IsarMissionDefinition.cs @@ -31,13 +31,16 @@ public IsarMissionDefinition(List tasks) Tasks = tasks; } - public IsarMissionDefinition(MissionRun missionRun, bool includeStartPose = false) + public IsarMissionDefinition(MissionRun missionRun, bool includeStartPose = false, string? mapName = null) { Name = missionRun.Name; - Tasks = missionRun.Tasks.Select(task => new IsarTaskDefinition(task, missionRun)).ToList(); - StartPose = includeStartPose && missionRun.Area.Deck.DefaultLocalizationPose != null ? new IsarPose(missionRun.Area.Deck.DefaultLocalizationPose.Pose) : null; - Undock = includeStartPose && missionRun.Area.Deck.DefaultLocalizationPose != null && missionRun.Area.Deck.DefaultLocalizationPose.DockingEnabled; - Dock = missionRun.Area.Deck.DefaultLocalizationPose != null && missionRun.Area.Deck.DefaultLocalizationPose.DockingEnabled && missionRun.IsReturnHomeMission(); + Tasks = missionRun.Tasks.Select(task => new IsarTaskDefinition(task, missionRun, mapName)).ToList(); + if (missionRun.InspectionArea != null) + { + StartPose = includeStartPose && missionRun.InspectionArea.DefaultLocalizationPose != null ? new IsarPose(missionRun.InspectionArea.DefaultLocalizationPose.Pose) : null; + Undock = includeStartPose && missionRun.InspectionArea.DefaultLocalizationPose != null && missionRun.InspectionArea.DefaultLocalizationPose.DockingEnabled; + Dock = missionRun.InspectionArea.DefaultLocalizationPose != null && missionRun.InspectionArea.DefaultLocalizationPose.DockingEnabled && missionRun.IsReturnHomeMission(); + } } } @@ -61,7 +64,7 @@ public struct IsarTaskDefinition [JsonPropertyName("zoom")] public IsarZoomDescription? Zoom { get; set; } - public IsarTaskDefinition(MissionTask missionTask, MissionRun missionRun) + public IsarTaskDefinition(MissionTask missionTask, MissionRun missionRun, string? mapName = null) { Id = missionTask.IsarTaskId; Type = MissionTask.ConvertMissionTaskTypeToIsarTaskType(missionTask.Type); @@ -69,7 +72,7 @@ public IsarTaskDefinition(MissionTask missionTask, MissionRun missionRun) Tag = missionTask.TagId; Zoom = missionTask.IsarZoomDescription; - if (missionTask.Inspection != null) Inspection = new IsarInspectionDefinition(missionTask.Inspection, missionRun); + if (missionTask.Inspection != null) Inspection = new IsarInspectionDefinition(missionTask.Inspection, missionRun, mapName); } } @@ -87,7 +90,7 @@ public struct IsarInspectionDefinition [JsonPropertyName("metadata")] public Dictionary? Metadata { get; set; } - public IsarInspectionDefinition(Inspection inspection, MissionRun missionRun) + public IsarInspectionDefinition(Inspection inspection, MissionRun missionRun, string? mapName = null) { Type = inspection.InspectionType.ToString(); InspectionTarget = inspection.InspectionTarget != null ? new IsarPosition( @@ -99,7 +102,7 @@ public IsarInspectionDefinition(Inspection inspection, MissionRun missionRun) Duration = inspection.VideoDuration; Metadata = new Dictionary { - { "map", missionRun.Map?.MapName }, + { "map", mapName }, { "description", missionRun.Description }, { "estimated_duration", missionRun.EstimatedDuration?.ToString("D", CultureInfo.InvariantCulture) }, { "asset_code", missionRun.InstallationCode }, diff --git a/backend/api/Services/Models/IsarTask.cs b/backend/api/Services/Models/IsarTask.cs index 7ff14fbcc..189664341 100644 --- a/backend/api/Services/Models/IsarTask.cs +++ b/backend/api/Services/Models/IsarTask.cs @@ -37,7 +37,6 @@ public static IsarTaskType TaskTypeFromString(string isarClassName) "take_video" => IsarTaskType.TakeVideo, "take_thermal_image" => IsarTaskType.TakeThermalImage, "take_thermal_video" => IsarTaskType.TakeThermalVideo, - "localize" => IsarTaskType.Localize, "return_to_home" => IsarTaskType.ReturnToHome, "move_arm" => IsarTaskType.MoveArm, _ @@ -62,7 +61,6 @@ public enum IsarTaskStatus public enum IsarTaskType { - Localize, ReturnToHome, TakeImage, TakeVideo, diff --git a/backend/api/Services/ReturnToHomeService.cs b/backend/api/Services/ReturnToHomeService.cs index 269982635..84ff68258 100644 --- a/backend/api/Services/ReturnToHomeService.cs +++ b/backend/api/Services/ReturnToHomeService.cs @@ -8,25 +8,19 @@ public interface IReturnToHomeService public Task GetActiveReturnToHomeMissionRun(string robotId, bool readOnly = true); } - public class ReturnToHomeService(ILogger logger, IRobotService robotService, IMissionRunService missionRunService, IMapService mapService) : IReturnToHomeService + public class ReturnToHomeService(ILogger logger, IRobotService robotService, IMissionRunService missionRunService) : IReturnToHomeService { public async Task ScheduleReturnToHomeMissionRunIfNotAlreadyScheduledOrRobotIsHome(string robotId) { logger.LogInformation("Scheduling return to home mission if not already scheduled or the robot is home for robot {RobotId}", robotId); + var lastMissionRun = await missionRunService.ReadLastExecutedMissionRunByRobot(robotId); - if (await IsReturnToHomeMissionAlreadyScheduled(robotId)) + if (await IsReturnToHomeMissionAlreadyScheduled(robotId) || (lastMissionRun != null && (lastMissionRun.IsReturnHomeMission() || lastMissionRun.IsEmergencyMission()))) { logger.LogInformation("ReturnToHomeMission is already scheduled for Robot {RobotId}", robotId); return null; } - if (await IsRobotHome(robotId)) - { - logger.LogInformation("Robot {RobotId} is home, setting current area to null", robotId); - await robotService.UpdateCurrentArea(robotId, null); - return null; - } - MissionRun missionRun; try { missionRun = await ScheduleReturnToHomeMissionRun(robotId); } catch (Exception ex) when (ex is RobotNotFoundException or AreaNotFoundException or DeckNotFoundException or PoseNotFoundException or UnsupportedRobotCapabilityException or MissionRunNotFoundException) @@ -37,21 +31,12 @@ public class ReturnToHomeService(ILogger logger, IRobotServ return missionRun; } - private async Task IsRobotHome(string robotId) - { - var lastExecutedMissionRun = await missionRunService.ReadLastExecutedMissionRunByRobot(robotId, readOnly: true); - if (lastExecutedMissionRun is null) - { - logger.LogInformation("Could not find last executed mission run for robot {RobotId}, can not guarantee that the robot is in its home", robotId); - return false; - } - return lastExecutedMissionRun.IsReturnHomeMission(); - } private async Task IsReturnToHomeMissionAlreadyScheduled(string robotId) { return await missionRunService.PendingOrOngoingReturnToHomeMissionRunExists(robotId); } + private async Task ScheduleReturnToHomeMissionRun(string robotId) { var robot = await robotService.ReadById(robotId, readOnly: true); @@ -62,69 +47,25 @@ private async Task ScheduleReturnToHomeMissionRun(string robotId) throw new RobotNotFoundException(errorMessage); } Pose? return_to_home_pose; - Area? currentArea; + Deck? currentInspectionArea; if (robot.RobotCapabilities is not null && robot.RobotCapabilities.Contains(RobotCapabilitiesEnum.auto_return_to_home)) { var previousMissionRun = await missionRunService.ReadLastExecutedMissionRunByRobot(robot.Id, readOnly: true); - if (previousMissionRun is null) - { - string errorMessage = $"Unable to schedule a return to home mission as the robot {robot.Id} has no previous mission run"; - logger.LogError("{Message}", errorMessage); - throw new MissionRunNotFoundException(errorMessage); - } - - if (previousMissionRun.Area.Deck is null) - { - string errorMessage = $"Unable to schedule a return to home mission as the area {previousMissionRun.Area.Id} for robot {robot.Id} is not linked to a deck"; - logger.LogError("{Message}", errorMessage); - throw new DeckNotFoundException(errorMessage); - } - - if (previousMissionRun.Area.Deck.DefaultLocalizationPose is null) - { - logger.LogError( - "Unable to schedule a return to home mission as the current area {AreaId} for robot {RobotId} is linked to the deck {DeckId} which has no default pose", - previousMissionRun.Area.Id, robot.Id, previousMissionRun.Area.Deck.Id); - string errorMessage = - $"Unable to schedule a return to home mission as the current area {previousMissionRun.Area.Id} for robot {robot.Id} " - + $"is linked to the deck {previousMissionRun.Area.Deck.Id} which has no default pose"; - logger.LogError("{Message}", errorMessage); - throw new PoseNotFoundException(errorMessage); - } - currentArea = previousMissionRun.Area; - return_to_home_pose = new Pose(previousMissionRun.Area.Deck.DefaultLocalizationPose.Pose); + currentInspectionArea = previousMissionRun?.InspectionArea; + return_to_home_pose = previousMissionRun?.InspectionArea?.DefaultLocalizationPose?.Pose == null ? new Pose() : new Pose(previousMissionRun.InspectionArea.DefaultLocalizationPose.Pose); } else { - if (robot.CurrentArea is null) - { - string errorMessage = $"Unable to schedule a return to home mission as the robot {robot.Id} is not localized."; - logger.LogError("{Message}", errorMessage); - throw new AreaNotFoundException(errorMessage); - } - - if (robot.CurrentArea.Deck is null) - { - string errorMessage = $"Unable to schedule a return to home mission as the current area {robot.CurrentArea.Id} for robot {robot.Id} is not linked to a deck"; - logger.LogError("{Message}", errorMessage); - throw new DeckNotFoundException(errorMessage); - } - - if (robot.CurrentArea.Deck.DefaultLocalizationPose is null) - { - logger.LogError( - "Unable to schedule a return to home mission as the current area {AreaId} for robot {RobotId} is linked to the deck {DeckId} which has no default pose", - robot.CurrentArea.Id, robot.Id, robot.CurrentArea.Deck.Id); - string errorMessage = - $"Unable to schedule a return to home mission as the current area {robot.CurrentArea.Id} for robot {robot.Id} " - + $"is linked to the deck {robot.CurrentArea.Deck.Id} which has no default pose"; - logger.LogError("{Message}", errorMessage); - throw new PoseNotFoundException(errorMessage); - } - currentArea = robot.CurrentArea; - return_to_home_pose = new Pose(robot.CurrentArea.Deck.DefaultLocalizationPose.Pose); + currentInspectionArea = robot.CurrentInspectionArea; + return_to_home_pose = robot.CurrentInspectionArea?.DefaultLocalizationPose?.Pose == null ? new Pose() : new Pose(robot.CurrentInspectionArea.DefaultLocalizationPose.Pose); } + if (currentInspectionArea == null) + { + string errorMessage = $"Robot with ID {robotId} could return home as it did not have an inspection area"; + logger.LogError("{Message}", errorMessage); + throw new DeckNotFoundException(errorMessage); + } var returnToHomeMissionRun = new MissionRun { @@ -132,21 +73,19 @@ private async Task ScheduleReturnToHomeMissionRun(string robotId) Robot = robot, InstallationCode = robot.CurrentInstallation.InstallationCode, MissionRunType = MissionRunType.ReturnHome, - Area = currentArea, + InspectionArea = currentInspectionArea!, Status = MissionStatus.Pending, DesiredStartTime = DateTime.UtcNow, Tasks = [ new(return_to_home_pose, MissionTaskType.ReturnHome) - ], - Map = new MapMetadata() + ] }; - await mapService.AssignMapToMission(returnToHomeMissionRun); var missionRun = await missionRunService.Create(returnToHomeMissionRun, false); logger.LogInformation( "Scheduled a mission for the robot {RobotName} to return to home location on deck {DeckName}", - robot.Name, currentArea.Deck.Name); + robot.Name, currentInspectionArea?.Name); return missionRun; } diff --git a/backend/api/Services/RobotService.cs b/backend/api/Services/RobotService.cs index a1536df48..1aef0eb6c 100644 --- a/backend/api/Services/RobotService.cs +++ b/backend/api/Services/RobotService.cs @@ -25,7 +25,7 @@ public interface IRobotService public Task UpdateRobotPose(string robotId, Pose pose); public Task UpdateRobotIsarConnected(string robotId, bool isarConnected); public Task UpdateCurrentMissionId(string robotId, string? missionId); - public Task UpdateCurrentArea(string robotId, string? areaId); + public Task UpdateCurrentInspectionArea(string robotId, string? inspectionAreaId); public Task UpdateDeprecated(string robotId, bool deprecated); public Task UpdateMissionQueueFrozen(string robotId, bool missionQueueFrozen); public Task UpdateFlotillaStatus(string robotId, RobotFlotillaStatus status); @@ -45,13 +45,13 @@ public class RobotService( ISignalRService signalRService, IAccessRoleService accessRoleService, IInstallationService installationService, - IAreaService areaService) : IRobotService + IDeckService deckService) : IRobotService { public async Task Create(Robot newRobot) { - if (newRobot.CurrentArea is not null) context.Entry(newRobot.CurrentArea).State = EntityState.Unchanged; if (newRobot.CurrentInstallation != null) context.Entry(newRobot.CurrentInstallation).State = EntityState.Unchanged; + if (newRobot.CurrentInspectionArea != null) context.Entry(newRobot.CurrentInspectionArea).State = EntityState.Unchanged; if (newRobot.Model != null) context.Entry(newRobot.Model).State = EntityState.Unchanged; await context.Robots.AddAsync(newRobot); @@ -72,20 +72,20 @@ public async Task CreateFromQuery(CreateRobotQuery robotQuery) throw new DbUpdateException($"Could not create new robot in database as installation {robotQuery.CurrentInstallationCode} doesn't exist"); } - Area? area = null; - if (robotQuery.CurrentAreaName is not null) + Deck? inspectionArea = null; + if (robotQuery.CurrentInspectionAreaName is not null) { - area = await areaService.ReadByInstallationAndName(robotQuery.CurrentInstallationCode, robotQuery.CurrentAreaName, readOnly: true); - if (area is null) + inspectionArea = await deckService.ReadByInstallationAndName(robotQuery.CurrentInstallationCode, robotQuery.CurrentInspectionAreaName, readOnly: true); + if (inspectionArea is null) { - logger.LogError("Area '{AreaName}' does not exist in installation {CurrentInstallation}", robotQuery.CurrentAreaName, robotQuery.CurrentInstallationCode); - throw new DbUpdateException($"Could not create new robot in database as area '{robotQuery.CurrentAreaName}' does not exist in installation {robotQuery.CurrentInstallationCode}"); + logger.LogError("Inspection area '{CurrentDeckName}' does not exist in installation {CurrentInstallation}", robotQuery.CurrentInspectionAreaName, robotQuery.CurrentInstallationCode); + throw new DbUpdateException($"Could not create new robot in database as inspection area '{robotQuery.CurrentInspectionAreaName}' does not exist in installation {robotQuery.CurrentInstallationCode}"); } } - var newRobot = new Robot(robotQuery, installation, robotModel, area); + var newRobot = new Robot(robotQuery, installation, robotModel, inspectionArea); - if (newRobot.CurrentArea is not null) context.Entry(newRobot.CurrentArea).State = EntityState.Unchanged; + if (newRobot.CurrentInspectionArea is not null) context.Entry(newRobot.CurrentInspectionArea).State = EntityState.Unchanged; if (newRobot.CurrentInstallation != null) context.Entry(newRobot.CurrentInstallation).State = EntityState.Unchanged; if (newRobot.Model != null) context.Entry(newRobot.Model).State = EntityState.Unchanged; @@ -219,17 +219,17 @@ public async Task UpdateCurrentMissionId(string robotId, string? currentM return robot; } - public async Task UpdateCurrentArea(string robotId, string? areaId) + public async Task UpdateCurrentInspectionArea(string robotId, string? inspectionAreaId) { - logger.LogInformation("Updating current area for robot with Id {robotId} to area with Id {areaId}", robotId, areaId); - if (areaId is null) { return await UpdateRobotProperty(robotId, "CurrentArea", null); } - var area = await areaService.ReadById(areaId, readOnly: true); + logger.LogInformation("Updating current inspection area for robot with Id {robotId} to inspection area with Id {areaId}", robotId, inspectionAreaId); + if (inspectionAreaId is null) { return await UpdateRobotProperty(robotId, "CurrentInspectionArea", null); } + var area = await deckService.ReadById(inspectionAreaId, readOnly: true); if (area is null) { - logger.LogError("Could not find area '{AreaId}' setting robot '{IsarId}' area to null", areaId, robotId); - return await UpdateRobotProperty(robotId, "CurrentArea", null); + logger.LogError("Could not find inspection area '{InspectionAreaId}' setting robot '{IsarId}' inspection area to null", inspectionAreaId, robotId); + return await UpdateRobotProperty(robotId, "CurrentInspectionArea", null); } - return await UpdateRobotProperty(robotId, "CurrentArea", area); + return await UpdateRobotProperty(robotId, "CurrentInspectionArea", area); } public async Task UpdateDeprecated(string robotId, bool deprecated) { return await UpdateRobotProperty(robotId, "Deprecated", deprecated); } @@ -260,7 +260,7 @@ public async Task> ReadAllActivePlants(bool readOnly = true) public async Task Update(Robot robot) { - if (robot.CurrentArea is not null) context.Entry(robot.CurrentArea).State = EntityState.Unchanged; + if (robot.CurrentInspectionArea is not null) context.Entry(robot.CurrentInspectionArea).State = EntityState.Unchanged; context.Entry(robot.Model).State = EntityState.Unchanged; var entry = context.Update(robot); @@ -300,16 +300,13 @@ private IQueryable GetRobotsWithSubModels(bool readOnly = true) .Include(r => r.Documentation) .Include(r => r.Model) .Include(r => r.CurrentInstallation) - .Include(r => r.CurrentArea) - .ThenInclude(area => area != null ? area.Deck : null) - .Include(r => r.CurrentArea) - .ThenInclude(area => area != null ? area.Plant : null) - .Include(r => r.CurrentArea) - .ThenInclude(area => area != null ? area.Installation : null) - .Include(r => r.CurrentArea) - .ThenInclude(area => area != null ? area.Deck : null) + .Include(r => r.CurrentInspectionArea) .ThenInclude(deck => deck != null ? deck.DefaultLocalizationPose : null) .ThenInclude(defaultLocalizationPose => defaultLocalizationPose != null ? defaultLocalizationPose.Pose : null) + .Include(r => r.CurrentInspectionArea) + .ThenInclude(area => area != null ? area.Plant : null) + .Include(r => r.CurrentInspectionArea) + .ThenInclude(area => area != null ? area.Installation : null) #pragma warning disable CA1304 .Where((r) => r.CurrentInstallation == null || r.CurrentInstallation.InstallationCode == null || accessibleInstallationCodes.Result.Contains(r.CurrentInstallation.InstallationCode.ToUpper())); #pragma warning restore CA1304 @@ -369,7 +366,7 @@ private void NotifySignalROfUpdatedRobot(Robot robot, Installation installation) public void DetachTracking(Robot robot) { if (robot.CurrentInstallation != null) installationService.DetachTracking(robot.CurrentInstallation); - if (robot.CurrentArea != null) areaService.DetachTracking(robot.CurrentArea); + if (robot.CurrentInspectionArea != null) deckService.DetachTracking(robot.CurrentInspectionArea); if (robot.Model != null) robotModelService.DetachTracking(robot.Model); context.Entry(robot).State = EntityState.Detached; } diff --git a/backend/api/Utilities/Exceptions.cs b/backend/api/Utilities/Exceptions.cs index 1b92b4b39..df3b5a085 100644 --- a/backend/api/Utilities/Exceptions.cs +++ b/backend/api/Utilities/Exceptions.cs @@ -18,9 +18,6 @@ public MissionException(string message, int isarStatusCode) : base(message) public class MissionSourceTypeException(string message) : Exception(message) { } - public class OngoingMissionNotLocalizationException(string message) : Exception(message) - { - } public class SourceException(string message) : Exception(message) { @@ -106,10 +103,6 @@ public class IsarCommunicationException(string message) : Exception(message) { } - public class LocalizationFailedException(string message) : Exception(message) - { - } - public class ReturnToHomeMissionFailedToScheduleException(string message) : Exception(message) { } diff --git a/frontend/src/api/ApiCaller.tsx b/frontend/src/api/ApiCaller.tsx index 57565d545..2d11bfafe 100644 --- a/frontend/src/api/ApiCaller.tsx +++ b/frontend/src/api/ApiCaller.tsx @@ -165,7 +165,7 @@ export class BackendAPICaller { }) } - if (parameters.area) path = path + 'Area=' + parameters.area + '&' + if (parameters.inspectionArea) path = path + 'InspectionArea=' + parameters.inspectionArea + '&' if (parameters.pageNumber) path = path + 'PageNumber=' + parameters.pageNumber + '&' if (parameters.pageSize) path = path + 'PageSize=' + parameters.pageSize + '&' if (parameters.orderBy) path = path + 'OrderBy=' + parameters.orderBy + '&' @@ -208,7 +208,7 @@ export class BackendAPICaller { const installationCode: string | null = BackendAPICaller.installationCode if (installationCode) path = path + 'InstallationCode=' + installationCode + '&' - if (parameters.area) path = path + 'Area=' + parameters.area + '&' + if (parameters.inspectionArea) path = path + 'InspectionArea=' + parameters.inspectionArea + '&' if (parameters.sourceId) path = path + 'SourceId=' + parameters.sourceId + '&' if (parameters.pageNumber) path = path + 'PageNumber=' + parameters.pageNumber + '&' if (parameters.pageSize) path = path + 'PageSize=' + parameters.pageSize + '&' @@ -290,7 +290,6 @@ export class BackendAPICaller { robotId: desiredRobot[0].id, missionSourceId: missionSourceId, installationCode: installationCode, - areaName: '', } const result = await BackendAPICaller.POST(path, body).catch( BackendAPICaller.handleError('POST', path) diff --git a/frontend/src/components/Contexts/MissionDefinitionsContext.tsx b/frontend/src/components/Contexts/MissionDefinitionsContext.tsx index 431722d14..d85e736a1 100644 --- a/frontend/src/components/Contexts/MissionDefinitionsContext.tsx +++ b/frontend/src/components/Contexts/MissionDefinitionsContext.tsx @@ -62,7 +62,6 @@ const useMissionDefinitions = (): IMissionDefinitionsContext => { }) registerEvent(SignalREventLabels.missionDefinitionDeleted, (username: string, message: string) => { const mDef: MissionDefinition = JSON.parse(message) - if (!mDef.area) return setMissionDefinitions((oldMissionDefs) => { const oldListCopy = [...oldMissionDefs] const queueIndex = oldListCopy.findIndex((m) => m.id === mDef.id) diff --git a/frontend/src/components/Contexts/MissionRunsContext.tsx b/frontend/src/components/Contexts/MissionRunsContext.tsx index 917aa5157..b1747f391 100644 --- a/frontend/src/components/Contexts/MissionRunsContext.tsx +++ b/frontend/src/components/Contexts/MissionRunsContext.tsx @@ -178,8 +178,10 @@ const useMissionRuns = (): IMissionRunsContext => { const [filteredMissionQueue, setFilteredMissionQueue] = useState([]) const [filteredOngoingMissions, setFilteredOngoingMissions] = useState([]) useEffect(() => { - setFilteredOngoingMissions(ongoingMissions.filter((m) => m.area?.installationCode === installationCode)) - setFilteredMissionQueue(missionQueue.filter((m) => m.area?.installationCode === installationCode)) + setFilteredOngoingMissions( + ongoingMissions.filter((m) => m.inspectionArea?.installationCode === installationCode) + ) + setFilteredMissionQueue(missionQueue.filter((m) => m.inspectionArea?.installationCode === installationCode)) // eslint-disable-next-line react-hooks/exhaustive-deps }, [installationCode, ongoingMissions, missionQueue]) diff --git a/frontend/src/components/Displays/ConfirmScheduleDialogs/LocalizationVerification/ScheduleMissionWithLocalizationVerification.tsx b/frontend/src/components/Displays/ConfirmScheduleDialogs/LocalizationVerification/ScheduleMissionWithLocalizationVerification.tsx index e92305c8c..c0be74265 100644 --- a/frontend/src/components/Displays/ConfirmScheduleDialogs/LocalizationVerification/ScheduleMissionWithLocalizationVerification.tsx +++ b/frontend/src/components/Displays/ConfirmScheduleDialogs/LocalizationVerification/ScheduleMissionWithLocalizationVerification.tsx @@ -38,7 +38,8 @@ export const ScheduleMissionWithLocalizationVerificationDialog = ({ const ongoingLocalizationMissionOnSameDeckExists = ongoingMissions.filter( - (mission) => mission.robot?.id === selectedRobot?.id && mission.area?.deckName === unikMissionDeckNames[0] + (mission) => + mission.robot?.id === selectedRobot?.id && mission.inspectionArea?.deckName === unikMissionDeckNames[0] ).length > 0 useEffect(() => { @@ -50,8 +51,8 @@ export const ScheduleMissionWithLocalizationVerificationDialog = ({ if ( (unikMissionDeckNames.length === 1 && - selectedRobot.currentArea?.deckName && - unikMissionDeckNames[0] === selectedRobot?.currentArea?.deckName) || + selectedRobot.currentInspectionArea?.deckName && + unikMissionDeckNames[0] === selectedRobot?.currentInspectionArea?.deckName) || ongoingLocalizationMissionOnSameDeckExists ) { scheduleMissions() @@ -68,18 +69,18 @@ export const ScheduleMissionWithLocalizationVerificationDialog = ({ return } - if (!selectedRobot.currentArea?.deckName && !ongoingLocalizationMissionOnSameDeckExists) { + if (!selectedRobot.currentInspectionArea?.deckName && !ongoingLocalizationMissionOnSameDeckExists) { setDialogToOpen(DialogTypes.verifyDeck) return } - if (unikMissionDeckNames[0] !== selectedRobot.currentArea?.deckName) { + if (unikMissionDeckNames[0] !== selectedRobot.currentInspectionArea?.deckName) { setDialogToOpen(DialogTypes.conflictingRobotDeck) return } // To ignore scheduleMissions dependency // eslint-disable-next-line react-hooks/exhaustive-deps - }, [unikMissionDeckNames, selectedRobot?.currentArea?.deckName]) + }, [unikMissionDeckNames, selectedRobot?.currentInspectionArea?.deckName]) return ( <> @@ -97,7 +98,7 @@ export const ScheduleMissionWithLocalizationVerificationDialog = ({ {dialogToOpen === DialogTypes.conflictingRobotDeck && ( )} diff --git a/frontend/src/components/Displays/MissionButtons/MissionRestartButton.tsx b/frontend/src/components/Displays/MissionButtons/MissionRestartButton.tsx index 756c0bd88..92cb7e648 100644 --- a/frontend/src/components/Displays/MissionButtons/MissionRestartButton.tsx +++ b/frontend/src/components/Displays/MissionButtons/MissionRestartButton.tsx @@ -115,7 +115,7 @@ export const MissionRestartButton = ({ mission, hasFailedTasks, smallButton }: M scheduleMissions={() => startReRun(selectedRerunOption!)} closeDialog={() => setIsLocationVerificationOpen(false)} robotId={mission.robot.id} - missionDeckNames={[mission.area?.deckName ?? '']} + missionDeckNames={[mission.inspectionArea?.deckName ?? '']} /> )} diff --git a/frontend/src/components/Displays/MissionDisplays/MissionAreaDispaly.tsx b/frontend/src/components/Displays/MissionDisplays/MissionAreaDispaly.tsx deleted file mode 100644 index e89a468c3..000000000 --- a/frontend/src/components/Displays/MissionDisplays/MissionAreaDispaly.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Typography } from '@equinor/eds-core-react' -import { useLanguageContext } from 'components/Contexts/LanguageContext' -import { AttributeTitleTypography } from 'components/Styles/StyledComponents' -import { Mission } from 'models/Mission' -import styled from 'styled-components' - -const StyledAreaDisplay = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; -` - -export const MissionAreaDisplay = ({ mission }: { mission: Mission }) => { - const { TranslateText } = useLanguageContext() - return ( - - {TranslateText('Area')} - {mission.area?.areaName || 'N/A'} - - ) -} diff --git a/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueCard.tsx b/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueCard.tsx index 5e8a6d9bf..6de0338d4 100644 --- a/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueCard.tsx +++ b/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueCard.tsx @@ -174,9 +174,8 @@ const MissionDetails = ({ mission }: { mission: Mission }) => { return translateNotAvailable } - const area = `${TranslateText('Area')}: ${mission.area?.areaName ?? TranslateText('Not available')}` const tasks = `${TranslateText('Tasks')}: ${mission.tasks.length}` - const missionDetails = `${area} | ${tasks} | ${estimatedDuration()}` + const missionDetails = `${tasks} | ${estimatedDuration()}` return ( diff --git a/frontend/src/components/Pages/FrontPage/MissionOverview/OngoingMissionCard.tsx b/frontend/src/components/Pages/FrontPage/MissionOverview/OngoingMissionCard.tsx index bf4817753..96b4c2e20 100644 --- a/frontend/src/components/Pages/FrontPage/MissionOverview/OngoingMissionCard.tsx +++ b/frontend/src/components/Pages/FrontPage/MissionOverview/OngoingMissionCard.tsx @@ -11,7 +11,6 @@ import { TaskType } from 'models/Task' import { StyledButton } from 'components/Styles/StyledComponents' import { useLanguageContext } from 'components/Contexts/LanguageContext' import { Icons } from 'utils/icons' -import { MissionAreaDisplay } from 'components/Displays/MissionDisplays/MissionAreaDispaly' interface MissionProps { mission: Mission @@ -95,7 +94,6 @@ export const OngoingMissionCard = ({ mission }: MissionProps): JSX.Element => { - { - diff --git a/frontend/src/components/Pages/InspectionPage/DeckCards.tsx b/frontend/src/components/Pages/InspectionPage/DeckCards.tsx index 25f65f261..e6aa94359 100644 --- a/frontend/src/components/Pages/InspectionPage/DeckCards.tsx +++ b/frontend/src/components/Pages/InspectionPage/DeckCards.tsx @@ -54,11 +54,6 @@ const DeckCard = ({ deckData, onClickDeck, selectedDeck, handleScheduleAll }: De if (deckData.inspections.length === 0) queueMissionsTooltip = TranslateText('No planned inspection') else if (isScheduleMissionsDisabled) queueMissionsTooltip = TranslateText('No robot available') - const formattedAreaNames = deckData.areas - .map((area) => area.areaName.toLocaleUpperCase()) - .sort() - .join(' | ') - return ( @@ -83,7 +78,6 @@ const DeckCard = ({ deckData, onClickDeck, selectedDeck, handleScheduleAll }: De ))} - {deckData.areas && {formattedAreaNames}} {deckData.inspections && ( )} diff --git a/frontend/src/components/Pages/InspectionPage/InspectionSection.tsx b/frontend/src/components/Pages/InspectionPage/InspectionSection.tsx index 5cffa2d0b..7d701f335 100644 --- a/frontend/src/components/Pages/InspectionPage/InspectionSection.tsx +++ b/frontend/src/components/Pages/InspectionPage/InspectionSection.tsx @@ -46,7 +46,9 @@ export const InspectionSection = () => { const deckInspections: DeckInspectionTuple[] = decks?.map(({ areas, deck }) => { - const missionDefinitionsInDeck = missionDefinitions.filter((m) => m.area?.deckName === deck.deckName) + const missionDefinitionsInDeck = missionDefinitions.filter( + (m) => m.inspectionArea?.deckName === deck.deckName + ) return { inspections: missionDefinitionsInDeck.map((m) => { return { diff --git a/frontend/src/components/Pages/InspectionPage/InspectionTable.tsx b/frontend/src/components/Pages/InspectionPage/InspectionTable.tsx index 560cf1f6d..782766bd4 100644 --- a/frontend/src/components/Pages/InspectionPage/InspectionTable.tsx +++ b/frontend/src/components/Pages/InspectionPage/InspectionTable.tsx @@ -47,7 +47,6 @@ enum InspectionTableColumns { Status = 'Status', Name = 'Name', Description = 'Description', - Area = 'Area', LastCompleted = 'LastCompleted', Deadline = 'Deadline', AddToQueue = 'AddToQueue', @@ -198,7 +197,6 @@ const InspectionRow = ({ inspection, openDialog, setMissions, openScheduledDialo {mission.comment} - {mission.area ? mission.area.areaName : '-'} {lastCompleted} {inspection.deadline diff --git a/frontend/src/components/Pages/InspectionPage/ScheduleMissionDialogs.tsx b/frontend/src/components/Pages/InspectionPage/ScheduleMissionDialogs.tsx index b7cf56fe1..0c9d4e22b 100644 --- a/frontend/src/components/Pages/InspectionPage/ScheduleMissionDialogs.tsx +++ b/frontend/src/components/Pages/InspectionPage/ScheduleMissionDialogs.tsx @@ -200,7 +200,7 @@ export const ScheduleMissionDialog = (props: IProps): JSX.Element => { scheduleMissions={scheduleMissions} closeDialog={closeScheduleDialogs} robotId={selectedRobot!.id} - missionDeckNames={props.selectedMissions.map((mission) => mission.area?.deckName ?? '')} + missionDeckNames={props.selectedMissions.map((mission) => mission.inspectionArea?.deckName ?? '')} /> )} diff --git a/frontend/src/components/Pages/MissionDefinitionPage/MissionDefinitionPage.tsx b/frontend/src/components/Pages/MissionDefinitionPage/MissionDefinitionPage.tsx index 584466dea..204603a10 100644 --- a/frontend/src/components/Pages/MissionDefinitionPage/MissionDefinitionPage.tsx +++ b/frontend/src/components/Pages/MissionDefinitionPage/MissionDefinitionPage.tsx @@ -80,21 +80,17 @@ const MissionDefinitionPageBody = ({ missionDefinition }: { missionDefinition: M <> - {mission.name} - - {mission.area?.areaName} - {mission.robot.name} diff --git a/frontend/src/components/Pages/MissionHistoryPage/MissionHistoryView.tsx b/frontend/src/components/Pages/MissionHistoryPage/MissionHistoryView.tsx index 6eebc0e2e..e97a36f69 100644 --- a/frontend/src/components/Pages/MissionHistoryPage/MissionHistoryView.tsx +++ b/frontend/src/components/Pages/MissionHistoryPage/MissionHistoryView.tsx @@ -45,9 +45,6 @@ const HideColumnsOnSmallScreen = styled.div` #${InspectionTableColumns.Status} { display: none; } - #${InspectionTableColumns.Area} { - display: none; - } #${InspectionTableColumns.Robot} { display: none; } @@ -296,9 +293,6 @@ export const MissionHistoryView = ({ refreshInterval }: RefreshProps) => { {TranslateText('Name')} - - {TranslateText('Area')} - {TranslateText('Robot')} diff --git a/frontend/src/components/Pages/MissionPage/MapPosition/MissionMapView.tsx b/frontend/src/components/Pages/MissionPage/MapPosition/MissionMapView.tsx index a7f341e8e..b32b184d6 100644 --- a/frontend/src/components/Pages/MissionPage/MapPosition/MissionMapView.tsx +++ b/frontend/src/components/Pages/MissionPage/MapPosition/MissionMapView.tsx @@ -50,11 +50,11 @@ export const MissionMapView = ({ mission }: MissionProps) => { const updateMap = useCallback(() => { let context = mapCanvas.getContext('2d') - if (context === null) { + if (!context || !mission.map) { return } context.clearRect(0, 0, mapCanvas.width, mapCanvas.height) - context?.drawImage(mapImage, 0, 0) + context.drawImage(mapImage, 0, 0) placeTagsInMap(mission.tasks, mission.map!, mapCanvas, currentTaskOrder) if (missionRobot?.pose && mission.map) { placeRobotInMap(mission.map, mapCanvas, missionRobot.pose) diff --git a/frontend/src/components/Pages/MissionPage/MissionHeader/MissionHeader.tsx b/frontend/src/components/Pages/MissionPage/MissionHeader/MissionHeader.tsx index 31d82b6c7..4a2c93925 100644 --- a/frontend/src/components/Pages/MissionPage/MissionHeader/MissionHeader.tsx +++ b/frontend/src/components/Pages/MissionPage/MissionHeader/MissionHeader.tsx @@ -135,7 +135,6 @@ export const MissionHeader = ({ mission }: { mission: Mission }) => { const translatedBatteryLevel = TranslateText('Battery level') const translatedPressureLevel = TranslateText('Pressure level') const translatedDescription = TranslateText('Description') - const translatedArea = TranslateText('Area') const translatedTasks = TranslateText('Completed Tasks') const translatedStatus = TranslateText('Status') @@ -193,7 +192,6 @@ export const MissionHeader = ({ mission }: { mission: Mission }) => { {HeaderText(translatedStatus, '')} - {HeaderText(translatedArea, `${mission.area?.areaName}`)} {HeaderText(translatedTasks, `${numberOfCompletedTasks + '/' + mission.tasks.length}`)} diff --git a/frontend/src/models/Area.ts b/frontend/src/models/Area.ts index 08cff2670..088c7d3a3 100644 --- a/frontend/src/models/Area.ts +++ b/frontend/src/models/Area.ts @@ -1,5 +1,3 @@ -import { Pose } from './Pose' - export interface Area { id: string areaName: string @@ -7,5 +5,4 @@ export interface Area { plantName: string installationCode: string deckName: string - defaultLocalizationPose?: Pose } diff --git a/frontend/src/models/Mission.ts b/frontend/src/models/Mission.ts index 3dd03a8a4..06c09af23 100644 --- a/frontend/src/models/Mission.ts +++ b/frontend/src/models/Mission.ts @@ -1,5 +1,5 @@ import { MapMetadata } from './MapMetadata' -import { Area } from './Area' +import { Deck } from './Deck' import { Robot, placeholderRobot } from './Robot' import { Task } from './Task' @@ -32,7 +32,7 @@ export interface Mission { statusReason?: string comment?: string installationCode?: string - area?: Area + inspectionArea?: Deck robot: Robot status: MissionStatus isCompleted: boolean diff --git a/frontend/src/models/MissionDefinition.ts b/frontend/src/models/MissionDefinition.ts index e67a74d27..b2cb78511 100644 --- a/frontend/src/models/MissionDefinition.ts +++ b/frontend/src/models/MissionDefinition.ts @@ -1,4 +1,4 @@ -import { Area } from './Area' +import { Deck } from './Deck' import { Mission } from './Mission' export interface MissionDefinition { @@ -8,7 +8,7 @@ export interface MissionDefinition { comment?: string inspectionFrequency?: string lastSuccessfulRun?: Mission - area?: Area + inspectionArea?: Deck isDeprecated: boolean sourceId: string } diff --git a/frontend/src/models/MissionDefinitionQueryParameters.ts b/frontend/src/models/MissionDefinitionQueryParameters.ts index 3dacc1762..9e07b95a4 100644 --- a/frontend/src/models/MissionDefinitionQueryParameters.ts +++ b/frontend/src/models/MissionDefinitionQueryParameters.ts @@ -1,7 +1,7 @@ export interface MissionDefinitionQueryParameters { nameSearch?: string robotNameSearch?: string - area?: string + inspectionArea?: string sourceId?: string pageNumber?: number pageSize?: number diff --git a/frontend/src/models/MissionRunQueryParameters.ts b/frontend/src/models/MissionRunQueryParameters.ts index 7682976a0..3c0f95688 100644 --- a/frontend/src/models/MissionRunQueryParameters.ts +++ b/frontend/src/models/MissionRunQueryParameters.ts @@ -9,7 +9,7 @@ export interface MissionRunQueryParameters { robotNameSearch?: string tagSearch?: string inspectionTypes?: InspectionType[] - area?: string + inspectionArea?: string excludeLocalization?: boolean excludeReturnToHome?: boolean minStartTime?: number diff --git a/frontend/src/models/Robot.ts b/frontend/src/models/Robot.ts index 99e9f09da..22c1b1d7c 100644 --- a/frontend/src/models/Robot.ts +++ b/frontend/src/models/Robot.ts @@ -1,4 +1,4 @@ -import { Area } from './Area' +import { Deck } from './Deck' import { BatteryStatus } from './Battery' import { DocumentInfo } from './DocumentInfo' import { Installation, placeholderInstallation } from './Installation' @@ -40,7 +40,7 @@ export interface Robot { port?: number documentation?: DocumentInfo[] isarUri?: string - currentArea?: Area + currentInspectionArea?: Deck flotillaStatus?: RobotFlotillaStatus } export const placeholderRobot: Robot = {