From 17e69e32fb3991d509924851c7482fe96967a22d Mon Sep 17 00:00:00 2001 From: andchiind Date: Fri, 6 Dec 2024 13:54:38 +0100 Subject: [PATCH] Let user configure custom zoom for tasks --- .../api/Controllers/InspectionController.cs | 53 + .../Controllers/Models/CustomMissionQuery.cs | 5 + .../api/Database/Context/FlotillaDbContext.cs | 2 + backend/api/Database/Models/MissionTask.cs | 6 + .../Database/Models/TagInspectionMetadata.cs | 14 + ...09125610_AddInspectionTagTable.Designer.cs | 1369 +++++++++++++++++ .../20241209125610_AddInspectionTagTable.cs | 54 + .../FlotillaDbContextModelSnapshot.cs | 59 + backend/api/Services/EchoService.cs | 33 +- .../Services/Models/IsarMissionDefinition.cs | 21 +- 10 files changed, 1612 insertions(+), 4 deletions(-) create mode 100644 backend/api/Controllers/InspectionController.cs create mode 100644 backend/api/Database/Models/TagInspectionMetadata.cs create mode 100644 backend/api/Migrations/20241209125610_AddInspectionTagTable.Designer.cs create mode 100644 backend/api/Migrations/20241209125610_AddInspectionTagTable.cs diff --git a/backend/api/Controllers/InspectionController.cs b/backend/api/Controllers/InspectionController.cs new file mode 100644 index 000000000..39ffe10a4 --- /dev/null +++ b/backend/api/Controllers/InspectionController.cs @@ -0,0 +1,53 @@ +using Api.Controllers.Models; +using Api.Services; +using Api.Services.MissionLoaders; +using Api.Services.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers +{ + [ApiController] + [Route("inspection")] + public class InspectionController( + ILogger logger, + IEchoService echoService + ) : ControllerBase + { + /// + /// Updates the Flotilla metadata for an inspection tag + /// + /// + /// This query updates the Flotilla metadata for an inpection tag + /// + [HttpPost("{tagId}/tag-zoom")] + [Authorize(Roles = Role.Admin)] + [ProducesResponseType(typeof(TagInspectionMetadata), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> Create([FromRoute] string tagId, [FromBody] IsarZoomDescription zoom) + { + logger.LogInformation($"Updating zoom value for tag with ID {tagId}"); + + var newMetadata = new TagInspectionMetadata + { + TagId = tagId, + ZoomDescription = zoom + }; + + try + { + var metadata = await echoService.CreateOrUpdateTagInspectionMetadata(newMetadata); + + return metadata; + } + catch (Exception e) + { + logger.LogError(e, "Error while creating or updating inspection tag metadata"); + throw; + } + } + } +} diff --git a/backend/api/Controllers/Models/CustomMissionQuery.cs b/backend/api/Controllers/Models/CustomMissionQuery.cs index 9421e9ef7..8d027cdcd 100644 --- a/backend/api/Controllers/Models/CustomMissionQuery.cs +++ b/backend/api/Controllers/Models/CustomMissionQuery.cs @@ -1,4 +1,5 @@ using Api.Database.Models; +using Api.Services.Models; namespace Api.Controllers.Models { public struct CustomInspectionQuery @@ -22,6 +23,8 @@ public struct CustomTaskQuery public Pose RobotPose { get; set; } + public IsarZoomDescription? IsarZoomDescription { get; set; } + public CustomInspectionQuery? Inspection { get; set; } } @@ -44,5 +47,7 @@ public struct CustomMissionQuery public string? Comment { get; set; } public List Tasks { get; set; } + + public IsarZoomDescription? IsarZoomDescription { get; set; } } } diff --git a/backend/api/Database/Context/FlotillaDbContext.cs b/backend/api/Database/Context/FlotillaDbContext.cs index 1a03536f9..8dc00048f 100644 --- a/backend/api/Database/Context/FlotillaDbContext.cs +++ b/backend/api/Database/Context/FlotillaDbContext.cs @@ -1,4 +1,5 @@ using Api.Database.Models; +using Api.Services.MissionLoaders; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -26,6 +27,7 @@ public FlotillaDbContext(DbContextOptions options) : base(options) public DbSet DefaultLocalizationPoses => Set(); public DbSet AccessRoles => Set(); public DbSet UserInfos => Set(); + public DbSet TagInspectionMetadata => Set(); // Timeseries: public DbSet RobotPressureTimeseries => Set(); diff --git a/backend/api/Database/Models/MissionTask.cs b/backend/api/Database/Models/MissionTask.cs index d78fcdcd7..92f32c934 100644 --- a/backend/api/Database/Models/MissionTask.cs +++ b/backend/api/Database/Models/MissionTask.cs @@ -25,6 +25,7 @@ public MissionTask( Uri? tagLink, string? tagId, int? poseId, + IsarZoomDescription? zoomDescription = null, TaskStatus status = TaskStatus.NotStarted, MissionTaskType type = MissionTaskType.Inspection) { @@ -35,6 +36,7 @@ public MissionTask( TaskOrder = taskOrder; Status = status; Type = type; + IsarZoomDescription = zoomDescription; if (inspection != null) Inspection = new Inspection(inspection); } @@ -45,6 +47,7 @@ public MissionTask(CustomTaskQuery taskQuery) RobotPose = taskQuery.RobotPose; TaskOrder = taskQuery.TaskOrder; Status = TaskStatus.NotStarted; + IsarZoomDescription = taskQuery.IsarZoomDescription; if (taskQuery.Inspection is not null) { Inspection = new Inspection((CustomInspectionQuery)taskQuery.Inspection); @@ -98,6 +101,7 @@ public MissionTask(MissionTask copy, TaskStatus? status = null) RobotPose = new Pose(copy.RobotPose); PoseId = copy.PoseId; Status = status ?? copy.Status; + IsarZoomDescription = copy.IsarZoomDescription; if (copy.Inspection is not null) { Inspection = new Inspection(copy.Inspection, InspectionStatus.NotStarted); @@ -156,6 +160,8 @@ or TaskStatus.Failed public DateTime? EndTime { get; private set; } + public IsarZoomDescription? IsarZoomDescription { get; set; } + public Inspection? Inspection { get; set; } public void UpdateWithIsarInfo(IsarTask isarTask) diff --git a/backend/api/Database/Models/TagInspectionMetadata.cs b/backend/api/Database/Models/TagInspectionMetadata.cs new file mode 100644 index 000000000..8b651c0b6 --- /dev/null +++ b/backend/api/Database/Models/TagInspectionMetadata.cs @@ -0,0 +1,14 @@ +#nullable disable +using System.ComponentModel.DataAnnotations; +using Api.Services.Models; + +namespace Api.Services.MissionLoaders +{ + public class TagInspectionMetadata + { + [Key] + public string TagId { get; set; } + + public IsarZoomDescription ZoomDescription { get; set; } + } +} diff --git a/backend/api/Migrations/20241209125610_AddInspectionTagTable.Designer.cs b/backend/api/Migrations/20241209125610_AddInspectionTagTable.Designer.cs new file mode 100644 index 000000000..c1f3aa7af --- /dev/null +++ b/backend/api/Migrations/20241209125610_AddInspectionTagTable.Designer.cs @@ -0,0 +1,1369 @@ +// +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("20241209125610_AddInspectionTagTable")] + partial class AddInspectionTagTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .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("InspectionType") + .IsRequired() + .HasColumnType("text"); + + b.Property("InspectionUrl") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + 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("AreaId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + 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("AreaId"); + + b.HasIndex("LastSuccessfulRunId"); + + b.HasIndex("SourceId"); + + b.ToTable("MissionDefinitions"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("AreaId") + .IsRequired() + .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("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("AreaId"); + + 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("CurrentAreaId") + .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("CurrentAreaId"); + + 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.Area", "Area") + .WithMany() + .HasForeignKey("AreaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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.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") + .HasColumnType("text"); + + b1.Property("MapName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b1.HasKey("MissionRunId"); + + b1.ToTable("MissionRuns"); + + b1.WithOwner() + .HasForeignKey("MissionRunId"); + + b1.OwnsOne("Api.Database.Models.Boundary", "Boundary", b2 => + { + b2.Property("MapMetadataMissionRunId") + .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("MapMetadataMissionRunId"); + + b2.ToTable("MissionRuns"); + + b2.WithOwner() + .HasForeignKey("MapMetadataMissionRunId"); + }); + + b1.OwnsOne("Api.Database.Models.TransformationMatrices", "TransformationMatrices", b2 => + { + b2.Property("MapMetadataMissionRunId") + .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("MapMetadataMissionRunId"); + + b2.ToTable("MissionRuns"); + + b2.WithOwner() + .HasForeignKey("MapMetadataMissionRunId"); + }); + + b1.Navigation("Boundary") + .IsRequired(); + + b1.Navigation("TransformationMatrices") + .IsRequired(); + }); + + b.Navigation("Area"); + + b.Navigation("Map"); + + 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.Area", "CurrentArea") + .WithMany() + .HasForeignKey("CurrentAreaId"); + + 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.OwnsMany("Api.Database.Models.VideoStream", "VideoStreams", 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("ShouldRotate270Clockwise") + .HasColumnType("boolean"); + + b1.Property("Type") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b1.Property("Url") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b1.HasKey("Id"); + + b1.HasIndex("RobotId"); + + b1.ToTable("VideoStream"); + + b1.WithOwner() + .HasForeignKey("RobotId"); + }); + + b.Navigation("CurrentArea"); + + b.Navigation("CurrentInstallation"); + + b.Navigation("Documentation"); + + b.Navigation("Model"); + + b.Navigation("Pose") + .IsRequired(); + + b.Navigation("VideoStreams"); + }); + + 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/20241209125610_AddInspectionTagTable.cs b/backend/api/Migrations/20241209125610_AddInspectionTagTable.cs new file mode 100644 index 000000000..feeecb75a --- /dev/null +++ b/backend/api/Migrations/20241209125610_AddInspectionTagTable.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Api.Migrations +{ + /// + public partial class AddInspectionTagTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsarZoomDescription_ObjectHeight", + table: "MissionTasks", + type: "double precision", + nullable: true); + + migrationBuilder.AddColumn( + name: "IsarZoomDescription_ObjectWidth", + table: "MissionTasks", + type: "double precision", + nullable: true); + + migrationBuilder.CreateTable( + name: "TagInspectionMetadata", + columns: table => new + { + TagId = table.Column(type: "text", nullable: false), + ZoomDescription_ObjectWidth = table.Column(type: "double precision", nullable: true), + ZoomDescription_ObjectHeight = table.Column(type: "double precision", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TagInspectionMetadata", x => x.TagId); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TagInspectionMetadata"); + + migrationBuilder.DropColumn( + name: "IsarZoomDescription_ObjectHeight", + table: "MissionTasks"); + + migrationBuilder.DropColumn( + name: "IsarZoomDescription_ObjectWidth", + table: "MissionTasks"); + } + } +} diff --git a/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs b/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs index 0ce416e5f..2aabdc11f 100644 --- a/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs +++ b/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs @@ -650,6 +650,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -1043,6 +1053,27 @@ protected override void BuildModel(ModelBuilder modelBuilder) .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") @@ -1111,6 +1142,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Inspection"); + b.Navigation("IsarZoomDescription"); + b.Navigation("RobotPose") .IsRequired(); }); @@ -1292,6 +1325,32 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("VideoStreams"); }); + 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"); diff --git a/backend/api/Services/EchoService.cs b/backend/api/Services/EchoService.cs index 65ddb3087..5ff065f55 100644 --- a/backend/api/Services/EchoService.cs +++ b/backend/api/Services/EchoService.cs @@ -1,9 +1,12 @@ using System.Globalization; using System.Text.Json; using Api.Controllers.Models; +using Api.Database.Context; using Api.Database.Models; using Api.Services.MissionLoaders; +using Api.Services.Models; using Api.Utilities; +using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Abstractions; namespace Api.Services { @@ -13,10 +16,11 @@ public interface IEchoService public Task GetMissionById(string sourceMissionId); public Task> GetTasksForMission(string missionSourceId); public Task> GetPlantInfos(); + public Task CreateOrUpdateTagInspectionMetadata(TagInspectionMetadata metadata); } public class EchoService( - ILogger logger, IDownstreamApi echoApi, ISourceService sourceService, IStidService stidService) : IEchoService + ILogger logger, IDownstreamApi echoApi, ISourceService sourceService, IStidService stidService, FlotillaDbContext context) : IEchoService { public const string ServiceName = "EchoApi"; @@ -93,7 +97,7 @@ private async Task GetEchoMission(string echoMissionId) public async Task> GetTasksForMission(string missionSourceId) { var echoMission = await GetEchoMission(missionSourceId); - var missionTasks = echoMission.Tags.SelectMany(t => MissionTasksFromEchoTag(t)).ToList(); + var missionTasks = echoMission.Tags.Select(t => MissionTasksFromEchoTag(t)).SelectMany(task => task.Result).ToList(); return missionTasks; } @@ -269,7 +273,7 @@ List echoPlantInfoResponse return echoPlantInfos; } - public IList MissionTasksFromEchoTag(EchoTag echoTag) + public async Task> MissionTasksFromEchoTag(EchoTag echoTag) { var inspections = echoTag.Inspections .Select(inspection => new Inspection( @@ -292,6 +296,7 @@ public IList MissionTasksFromEchoTag(EchoTag echoTag) robotPose: echoTag.Pose, poseId: echoTag.PoseId, taskOrder: echoTag.PlanOrder, + zoomDescription: await FindInspectionZoom(echoTag), status: Database.Models.TaskStatus.NotStarted, type: MissionTaskType.Inspection )); @@ -299,5 +304,27 @@ public IList MissionTasksFromEchoTag(EchoTag echoTag) return missionTasks; } + + public async Task CreateOrUpdateTagInspectionMetadata(TagInspectionMetadata metadata) + { + var existingMetadata = await context.TagInspectionMetadata.Where(e => e.TagId == metadata.TagId).FirstOrDefaultAsync(); + if (existingMetadata == null) + { + await context.TagInspectionMetadata.AddAsync(metadata); + } + else + { + existingMetadata.ZoomDescription = metadata.ZoomDescription; + context.TagInspectionMetadata.Update(existingMetadata); + } + + await context.SaveChangesAsync(); + return metadata; + } + + private async Task FindInspectionZoom(EchoTag echoTag) + { + return (await context.TagInspectionMetadata.Where((e) => e.TagId == echoTag.TagId).FirstOrDefaultAsync())?.ZoomDescription; + } } } diff --git a/backend/api/Services/Models/IsarMissionDefinition.cs b/backend/api/Services/Models/IsarMissionDefinition.cs index 1573479f8..7d4347488 100644 --- a/backend/api/Services/Models/IsarMissionDefinition.cs +++ b/backend/api/Services/Models/IsarMissionDefinition.cs @@ -1,6 +1,8 @@ -using System.Globalization; +using System.ComponentModel.DataAnnotations; +using System.Globalization; using System.Text.Json.Serialization; using Api.Database.Models; +using Microsoft.EntityFrameworkCore; namespace Api.Services.Models { /// @@ -61,12 +63,17 @@ public struct IsarTaskDefinition [JsonPropertyName("inspection")] public IsarInspectionDefinition? Inspection { get; set; } + [JsonPropertyName("zoom")] + public IsarZoomDescription? Zoom { get; set; } + public IsarTaskDefinition(MissionTask missionTask, MissionRun missionRun) { Id = missionTask.IsarTaskId; Type = MissionTask.ConvertMissionTaskTypeToIsarTaskType(missionTask.Type); Pose = new IsarPose(missionTask.RobotPose); Tag = missionTask.TagId; + Zoom = missionTask.IsarZoomDescription; + if (missionTask.Inspection != null) Inspection = new IsarInspectionDefinition(missionTask.Inspection, missionRun); } } @@ -162,4 +169,16 @@ public readonly struct IsarPose(Pose pose) [JsonPropertyName("frame_name")] public string FrameName { get; } = "asset"; } + + [Owned] + public class IsarZoomDescription(double objectWidth, double objectHeight) + { + [Required] + [JsonPropertyName("objectWidth")] + public double ObjectWidth { get; set; } = objectWidth; + + [Required] + [JsonPropertyName("objectHeight")] + public double ObjectHeight { get; set; } = objectHeight; + } }