From 6e58c59624457168ae8bf92ccddbfcabbdce2c97 Mon Sep 17 00:00:00 2001 From: Christian de Jonge Date: Mon, 6 Mar 2023 13:47:55 +0100 Subject: [PATCH] Move definition of mission to robot interface --- .../apis/models/start_mission_definition.py | 5 +- .../apis/schedule/scheduling_controller.py | 45 +++++++++---- src/isar/mission_planner/echo_planner.py | 12 +++- src/isar/mission_planner/local_planner.py | 3 +- .../mission_planner_interface.py | 2 +- .../sequential_task_selector.py | 2 +- .../task_selector_interface.py | 2 +- src/isar/models/communication/message.py | 4 +- src/isar/models/mission/__init__.py | 2 - src/isar/models/mission/status.py | 21 ------- .../mission_metadata/mission_metadata.py | 2 +- .../mqtt/robot_status_publisher.py | 2 +- .../utilities/scheduling_utilities.py | 16 +++-- src/isar/state_machine/state_machine.py | 13 ++-- src/isar/state_machine/states/idle.py | 1 + src/isar/state_machine/states/monitor.py | 4 +- src/isar/storage/slimm_storage.py | 4 +- src/isar/storage/utilities.py | 7 +-- .../models/inspection/inspection.py | 2 +- .../models/mission/__init__.py | 12 ---- src/robot_interface/models/mission/mission.py | 20 ++++++ src/robot_interface/models/mission/status.py | 20 ++++++ src/robot_interface/models/mission/step.py | 2 +- .../models/mission/task.py} | 63 +++---------------- src/robot_interface/robot_interface.py | 25 +++++++- .../utilities/uuid_string_factory.py | 0 .../turtlebot/test_successful_mission.py | 4 +- .../models/test_start_mission_definition.py | 2 +- .../isar/services/readers/test_base_reader.py | 4 +- .../services/readers/test_mission_reader.py | 27 ++++---- .../echo/test_echo_service.py | 11 +++- .../isar/state_machine/states/test_monitor.py | 6 +- .../isar/state_machine/test_state_machine.py | 37 ++++++++--- tests/isar/storage/test_blob_storage.py | 4 +- tests/isar/storage/test_uploader.py | 17 +++-- tests/mocks/mission_definition.py | 3 +- tests/mocks/robot_interface.py | 12 ++-- tests/mocks/step.py | 2 +- 38 files changed, 237 insertions(+), 183 deletions(-) delete mode 100644 src/isar/models/mission/__init__.py delete mode 100644 src/isar/models/mission/status.py create mode 100644 src/robot_interface/models/mission/mission.py rename src/{isar/models/mission/mission.py => robot_interface/models/mission/task.py} (61%) rename src/{isar/services => robot_interface}/utilities/uuid_string_factory.py (100%) diff --git a/src/isar/apis/models/start_mission_definition.py b/src/isar/apis/models/start_mission_definition.py index 3959c5b3..0af42f09 100644 --- a/src/isar/apis/models/start_mission_definition.py +++ b/src/isar/apis/models/start_mission_definition.py @@ -6,16 +6,17 @@ from isar.apis.models.models import InputPose, InputPosition from isar.mission_planner.mission_planner_interface import MissionPlannerError -from isar.models.mission.mission import Mission, Task +from robot_interface.models.mission.mission import Mission from robot_interface.models.mission.step import ( - STEPS, DriveToPose, RecordAudio, + STEPS, TakeImage, TakeThermalImage, TakeThermalVideo, TakeVideo, ) +from robot_interface.models.mission.task import Task class InspectionTypes(str, Enum): diff --git a/src/isar/apis/schedule/scheduling_controller.py b/src/isar/apis/schedule/scheduling_controller.py index fe8111f1..51ddc9a1 100644 --- a/src/isar/apis/schedule/scheduling_controller.py +++ b/src/isar/apis/schedule/scheduling_controller.py @@ -1,23 +1,29 @@ import logging from http import HTTPStatus -from typing import Optional +from typing import List, Optional from alitra import Pose from fastapi import Body, HTTPException, Path from injector import inject from isar.apis.models import InputPose, StartMissionResponse -from isar.apis.models.models import ControlMissionResponse, RobotInfoResponse +from isar.apis.models.models import ( + ControlMissionResponse, + RobotInfoResponse, + TaskResponse, +) from isar.apis.models.start_mission_definition import ( StartMissionDefinition, to_isar_mission, ) from isar.config.settings import robot_settings, settings from isar.mission_planner.mission_planner_interface import MissionPlannerError -from isar.models.mission import Mission, Task +from isar.models.mission_metadata.mission_metadata import MissionMetadata from isar.services.utilities.scheduling_utilities import SchedulingUtilities from isar.state_machine.states_enum import States -from robot_interface.models.mission import DriveToPose +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.step import DriveToPose +from robot_interface.models.mission.task import Task class SchedulingController: @@ -70,10 +76,11 @@ def start_mission_by_id( ) self.logger.info(f"Starting mission with ISAR Mission ID: '{mission.id}'") + metadata: MissionMetadata = MissionMetadata(mission.id) self.scheduling_utilities.start_mission( - mission=mission, initial_pose=initial_pose_alitra + mission=mission, initial_pose=initial_pose_alitra, mission_metadata=metadata ) - return mission.api_response() + return self._api_response(mission) def start_mission( self, @@ -132,11 +139,12 @@ def start_mission( initial_pose.to_alitra_pose() if initial_pose else None ) + metadata: MissionMetadata = MissionMetadata(mission.id) self.logger.info(f"Starting mission: {mission.id}") self.scheduling_utilities.start_mission( - mission=mission, initial_pose=initial_pose_alitra + mission=mission, mission_metadata=metadata, initial_pose=initial_pose_alitra ) - return mission.api_response() + return self._api_response(mission) def pause_mission(self) -> ControlMissionResponse: self.logger.info("Received request to pause current mission") @@ -212,12 +220,14 @@ def drive_to( pose: Pose = target_pose.to_alitra_pose() step: DriveToPose = DriveToPose(pose=pose) mission: Mission = Mission(tasks=[Task(steps=[step])]) - + metadata: MissionMetadata = MissionMetadata(mission.id) self.logger.info( f"Starting drive to mission with ISAR Mission ID: '{mission.id}'" ) - self.scheduling_utilities.start_mission(mission=mission, initial_pose=None) - return mission.api_response() + self.scheduling_utilities.start_mission( + mission=mission, initial_pose=None, mission_metadata=metadata + ) + return self._api_response(mission) def get_info(self): return RobotInfoResponse( @@ -228,3 +238,16 @@ def get_info(self): robot_capabilities=robot_settings.CAPABILITIES, plant_short_name=settings.STID_PLANT_NAME, ) + + def _api_response(self, mission: Mission) -> StartMissionResponse: + return StartMissionResponse( + id=mission.id, + tasks=[self._task_api_response(task) for task in mission.tasks], + ) + + def _task_api_response(self, task: Task) -> TaskResponse: + steps: List[dict] = [] + for step in task.steps: + steps.append({"id": step.id, "type": step.__class__.__name__}) + + return TaskResponse(id=task.id, tag_id=task.tag_id, steps=steps) diff --git a/src/isar/mission_planner/echo_planner.py b/src/isar/mission_planner/echo_planner.py index 921b0b63..4e821023 100644 --- a/src/isar/mission_planner/echo_planner.py +++ b/src/isar/mission_planner/echo_planner.py @@ -13,12 +13,18 @@ MissionPlannerError, MissionPlannerInterface, ) -from isar.models.mission import Mission, Task from isar.services.auth.azure_credentials import AzureCredentials from isar.services.service_connections.request_handler import RequestHandler from isar.services.service_connections.stid.stid_service import StidService -from robot_interface.models.mission import DriveToPose, TakeImage, TakeThermalImage -from robot_interface.models.mission.step import TakeThermalVideo, TakeVideo +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.step import ( + DriveToPose, + TakeImage, + TakeThermalImage, + TakeThermalVideo, + TakeVideo, +) +from robot_interface.models.mission.task import Task class EchoPlanner(MissionPlannerInterface): diff --git a/src/isar/mission_planner/local_planner.py b/src/isar/mission_planner/local_planner.py index bdb88a95..2b1c80a0 100644 --- a/src/isar/mission_planner/local_planner.py +++ b/src/isar/mission_planner/local_planner.py @@ -10,8 +10,8 @@ MissionPlannerError, MissionPlannerInterface, ) -from isar.models.mission import Mission from isar.services.readers.base_reader import BaseReader, BaseReaderError +from robot_interface.models.mission.mission import Mission logger = logging.getLogger("api") @@ -27,7 +27,6 @@ def get_mission(self, mission_id) -> Mission: raise MissionPlannerError("There were no predefined missions") try: mission: Mission = missions[mission_id]["mission"] - mission.set_unique_id_and_metadata() return mission except KeyError as e: raise MissionNotFoundError( diff --git a/src/isar/mission_planner/mission_planner_interface.py b/src/isar/mission_planner/mission_planner_interface.py index 5a0f8942..76ccebc4 100644 --- a/src/isar/mission_planner/mission_planner_interface.py +++ b/src/isar/mission_planner/mission_planner_interface.py @@ -1,6 +1,6 @@ from abc import ABCMeta, abstractmethod -from isar.models.mission import Mission +from robot_interface.models.mission.mission import Mission class MissionPlannerInterface(metaclass=ABCMeta): diff --git a/src/isar/mission_planner/sequential_task_selector.py b/src/isar/mission_planner/sequential_task_selector.py index 1c01d923..c563eb64 100644 --- a/src/isar/mission_planner/sequential_task_selector.py +++ b/src/isar/mission_planner/sequential_task_selector.py @@ -4,7 +4,7 @@ TaskSelectorInterface, TaskSelectorStop, ) -from isar.models.mission import Task +from robot_interface.models.mission.task import Task class SequentialTaskSelector(TaskSelectorInterface): diff --git a/src/isar/mission_planner/task_selector_interface.py b/src/isar/mission_planner/task_selector_interface.py index 89f86859..d3d34f0e 100644 --- a/src/isar/mission_planner/task_selector_interface.py +++ b/src/isar/mission_planner/task_selector_interface.py @@ -1,7 +1,7 @@ from abc import ABCMeta, abstractmethod from typing import List -from isar.models.mission import Task +from robot_interface.models.mission.task import Task class TaskSelectorInterface(metaclass=ABCMeta): diff --git a/src/isar/models/communication/message.py b/src/isar/models/communication/message.py index 4fac44cd..bb81f088 100644 --- a/src/isar/models/communication/message.py +++ b/src/isar/models/communication/message.py @@ -3,10 +3,12 @@ from alitra import Pose -from isar.models.mission import Mission +from isar.models.mission_metadata.mission_metadata import MissionMetadata +from robot_interface.models.mission.mission import Mission @dataclass class StartMissionMessage: mission: Mission + mission_metadata: MissionMetadata initial_pose: Optional[Pose] diff --git a/src/isar/models/mission/__init__.py b/src/isar/models/mission/__init__.py deleted file mode 100644 index 94f5c906..00000000 --- a/src/isar/models/mission/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .mission import Mission, Task -from .status import MissionStatus, TaskStatus diff --git a/src/isar/models/mission/status.py b/src/isar/models/mission/status.py deleted file mode 100644 index c4e12fc5..00000000 --- a/src/isar/models/mission/status.py +++ /dev/null @@ -1,21 +0,0 @@ -from enum import Enum - - -class MissionStatus(str, Enum): - NotStarted: str = "not_started" - InProgress: str = "in_progress" - Paused: str = "paused" - Failed: str = "failed" - Cancelled: str = "cancelled" - Successful: str = "successful" - PartiallySuccessful: str = "partially_successful" - - -class TaskStatus(str, Enum): - NotStarted: str = "not_started" - InProgress: str = "in_progress" - Paused: str = "paused" - Failed: str = "failed" - Cancelled: str = "cancelled" - Successful: str = "successful" - PartiallySuccessful: str = "partially_successful" diff --git a/src/isar/models/mission_metadata/mission_metadata.py b/src/isar/models/mission_metadata/mission_metadata.py index 75f57f94..9b05c1ac 100644 --- a/src/isar/models/mission_metadata/mission_metadata.py +++ b/src/isar/models/mission_metadata/mission_metadata.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from datetime import date, datetime -from typing import Optional, Union +from typing import Optional from isar.config.settings import settings diff --git a/src/isar/services/service_connections/mqtt/robot_status_publisher.py b/src/isar/services/service_connections/mqtt/robot_status_publisher.py index d4612c2c..31fb6e4c 100644 --- a/src/isar/services/service_connections/mqtt/robot_status_publisher.py +++ b/src/isar/services/service_connections/mqtt/robot_status_publisher.py @@ -90,4 +90,4 @@ def run(self) -> None: self.logger.warning( "Failed to get robot status due to a communication exception" ) - time.sleep(settings.ROBOT_API_STATUS_POLL_INTERVAL) + time.sleep(settings.ROBOT_API_STATUS_POLL_INTERVAL) diff --git a/src/isar/services/utilities/scheduling_utilities.py b/src/isar/services/utilities/scheduling_utilities.py index f0bd051c..7ba3989e 100644 --- a/src/isar/services/utilities/scheduling_utilities.py +++ b/src/isar/services/utilities/scheduling_utilities.py @@ -17,10 +17,11 @@ MissionPlannerInterface, ) from isar.models.communication.message import StartMissionMessage -from isar.models.communication.queues import QueueIO, Queues, QueueTimeoutError -from isar.models.mission.mission import Mission +from isar.models.communication.queues import QueueIO, QueueTimeoutError, Queues +from isar.models.mission_metadata.mission_metadata import MissionMetadata from isar.services.utilities.queue_utilities import QueueUtilities from isar.state_machine.states_enum import States +from robot_interface.models.mission.mission import Mission class SchedulingUtilities: @@ -135,7 +136,12 @@ def verify_state_machine_ready_to_receive_mission(self, state: States) -> bool: return is_state_machine_ready_to_receive_mission - def start_mission(self, mission: Mission, initial_pose: Optional[Pose]) -> None: + def start_mission( + self, + mission: Mission, + mission_metadata: MissionMetadata, + initial_pose: Optional[Pose], + ) -> None: """Start mission Raises @@ -146,7 +152,9 @@ def start_mission(self, mission: Mission, initial_pose: Optional[Pose]) -> None: try: self._send_command( StartMissionMessage( - mission=deepcopy(mission), initial_pose=initial_pose + mission=deepcopy(mission), + mission_metadata=deepcopy(mission_metadata), + initial_pose=initial_pose, ), self.queues.start_mission, ) diff --git a/src/isar/state_machine/state_machine.py b/src/isar/state_machine/state_machine.py index de2195fa..9b698808 100644 --- a/src/isar/state_machine/state_machine.py +++ b/src/isar/state_machine/state_machine.py @@ -19,8 +19,7 @@ ) from isar.models.communication.message import StartMissionMessage from isar.models.communication.queues.queues import Queues -from isar.models.mission import Mission, Task -from isar.models.mission.status import MissionStatus, TaskStatus +from isar.models.mission_metadata.mission_metadata import MissionMetadata from isar.state_machine.states import ( Idle, Initialize, @@ -32,8 +31,10 @@ ) from isar.state_machine.states_enum import States from robot_interface.models.initialize.initialize_params import InitializeParams -from robot_interface.models.mission import StepStatus +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.status import MissionStatus, StepStatus, TaskStatus from robot_interface.models.mission.step import Step +from robot_interface.models.mission.task import Task from robot_interface.robot_interface import RobotInterface from robot_interface.telemetry.mqtt_client import MqttClientInterface from robot_interface.utilities.json_service import EnhancedJSONEncoder @@ -194,6 +195,7 @@ def __init__( self.stopped: bool = False self.current_mission: Optional[Mission] = None + self.current_mission_metadata: Optional[MissionMetadata] = None self.current_task: Optional[Task] = None self.current_step: Optional[Step] = None self.initial_pose: Optional[Pose] = None @@ -394,9 +396,12 @@ def reset_state_machine(self) -> None: self.current_mission = None self.initial_pose = None - def start_mission(self, mission: Mission, initial_pose: Pose): + def start_mission( + self, mission: Mission, mission_metadata: MissionMetadata, initial_pose: Pose + ): """Starts a scheduled mission.""" self.current_mission = mission + self.current_mission_metadata = mission_metadata self.initial_pose = initial_pose self.task_selector.initialize(tasks=self.current_mission.tasks) diff --git a/src/isar/state_machine/states/idle.py b/src/isar/state_machine/states/idle.py index a8cf781b..464bcff5 100644 --- a/src/isar/state_machine/states/idle.py +++ b/src/isar/state_machine/states/idle.py @@ -31,6 +31,7 @@ def _run(self) -> None: if start_mission: self.state_machine.start_mission( mission=start_mission.mission, + mission_metadata=start_mission.mission_metadata, initial_pose=start_mission.initial_pose, ) transition = self.state_machine.mission_started # type: ignore diff --git a/src/isar/state_machine/states/monitor.py b/src/isar/state_machine/states/monitor.py index 76ce7144..87367ae7 100644 --- a/src/isar/state_machine/states/monitor.py +++ b/src/isar/state_machine/states/monitor.py @@ -13,7 +13,7 @@ ) from robot_interface.models.exceptions import RobotException from robot_interface.models.inspection.inspection import Inspection -from robot_interface.models.mission import InspectionStep, Step, StepStatus +from robot_interface.models.mission.step import InspectionStep, Step, StepStatus if TYPE_CHECKING: from isar.state_machine.state_machine import StateMachine @@ -99,7 +99,7 @@ def _queue_inspections_for_upload(self, current_step: InspectionStep) -> None: # A deepcopy is made to freeze the metadata before passing it to another thread # through the queue mission_metadata: MissionMetadata = deepcopy( - self.state_machine.current_mission.metadata + self.state_machine.current_mission_metadata ) for inspection in inspections: diff --git a/src/isar/storage/slimm_storage.py b/src/isar/storage/slimm_storage.py index 4a9544d9..76c293c6 100644 --- a/src/isar/storage/slimm_storage.py +++ b/src/isar/storage/slimm_storage.py @@ -116,7 +116,7 @@ def _construct_multiform_request_image( "ImageMetadata.Timestamp": inspection.metadata.start_time.isoformat(), # noqa: E501 "ImageMetadata.X": str(inspection.metadata.pose.position.x), "ImageMetadata.Y": str(inspection.metadata.pose.position.y), - "ImageMetadata.Y": str(inspection.metadata.pose.position.z), + "ImageMetadata.Z": str(inspection.metadata.pose.position.z), "ImageMetadata.CameraOrientation1": str(array_of_orientation[0]), "ImageMetadata.CameraOrientation2": str(array_of_orientation[1]), "ImageMetadata.CameraOrientation3": str(array_of_orientation[2]), @@ -158,7 +158,7 @@ def _construct_multiform_request_video( "VideoMetadata.Duration": str(inspection.metadata.duration), # type: ignore "VideoMetadata.X": str(inspection.metadata.pose.position.x), "VideoMetadata.Y": str(inspection.metadata.pose.position.y), - "VideoMetadata.Y": str(inspection.metadata.pose.position.z), + "VideoMetadata.Z": str(inspection.metadata.pose.position.z), "VideoMetadata.CameraOrientation1": str(array_of_orientation[0]), "VideoMetadata.CameraOrientation2": str(array_of_orientation[1]), "VideoMetadata.CameraOrientation3": str(array_of_orientation[2]), diff --git a/src/isar/storage/utilities.py b/src/isar/storage/utilities.py index b4a17a87..d6487079 100644 --- a/src/isar/storage/utilities.py +++ b/src/isar/storage/utilities.py @@ -1,12 +1,9 @@ import json -import logging from pathlib import Path -from typing import Any, Tuple +from typing import Tuple from isar.models.mission_metadata.mission_metadata import MissionMetadata -from isar.storage.storage_interface import StorageException -from robot_interface.models.inspection import ThermalVideo, Video -from robot_interface.models.inspection.inspection import Image, Inspection, ThermalImage +from robot_interface.models.inspection.inspection import Inspection from robot_interface.utilities.json_service import EnhancedJSONEncoder diff --git a/src/robot_interface/models/inspection/inspection.py b/src/robot_interface/models/inspection/inspection.py index 46f9908f..9c342db9 100644 --- a/src/robot_interface/models/inspection/inspection.py +++ b/src/robot_interface/models/inspection/inspection.py @@ -5,7 +5,7 @@ from alitra import Pose -from isar.services.utilities.uuid_string_factory import uuid4_string +from robot_interface.utilities.uuid_string_factory import uuid4_string @dataclass diff --git a/src/robot_interface/models/mission/__init__.py b/src/robot_interface/models/mission/__init__.py index 8d0000a9..e69de29b 100644 --- a/src/robot_interface/models/mission/__init__.py +++ b/src/robot_interface/models/mission/__init__.py @@ -1,12 +0,0 @@ -from .status import StepStatus -from .step import ( - DriveToPose, - InspectionStep, - MotionStep, - STEPS, - Step, - TakeImage, - TakeThermalImage, - TakeThermalVideo, - TakeVideo, -) diff --git a/src/robot_interface/models/mission/mission.py b/src/robot_interface/models/mission/mission.py new file mode 100644 index 00000000..76089829 --- /dev/null +++ b/src/robot_interface/models/mission/mission.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass, field +from typing import List + +from robot_interface.models.mission.status import MissionStatus +from robot_interface.models.mission.task import Task +from robot_interface.utilities.uuid_string_factory import uuid4_string + + +@dataclass +class Mission: + tasks: List[Task] + id: str = field(default_factory=uuid4_string, init=True) + status: MissionStatus = MissionStatus.NotStarted + + def _set_unique_id(self) -> None: + self.id: str = uuid4_string() + + def __post_init__(self) -> None: + if self.id is None: + self._set_unique_id() diff --git a/src/robot_interface/models/mission/status.py b/src/robot_interface/models/mission/status.py index c8402d8e..cc0ad13a 100644 --- a/src/robot_interface/models/mission/status.py +++ b/src/robot_interface/models/mission/status.py @@ -1,6 +1,16 @@ from enum import Enum +class MissionStatus(str, Enum): + NotStarted: str = "not_started" + InProgress: str = "in_progress" + Paused: str = "paused" + Failed: str = "failed" + Cancelled: str = "cancelled" + Successful: str = "successful" + PartiallySuccessful: str = "partially_successful" + + class StepStatus(str, Enum): NotStarted: str = "not_started" Successful: str = "successful" @@ -9,6 +19,16 @@ class StepStatus(str, Enum): Cancelled: str = "cancelled" +class TaskStatus(str, Enum): + NotStarted: str = "not_started" + InProgress: str = "in_progress" + Paused: str = "paused" + Failed: str = "failed" + Cancelled: str = "cancelled" + Successful: str = "successful" + PartiallySuccessful: str = "partially_successful" + + class RobotStatus(Enum): Available: str = "available" Busy: str = "busy" diff --git a/src/robot_interface/models/mission/step.py b/src/robot_interface/models/mission/step.py index 36a3b2af..4ad96c0f 100644 --- a/src/robot_interface/models/mission/step.py +++ b/src/robot_interface/models/mission/step.py @@ -3,7 +3,6 @@ from alitra import Pose, Position -from isar.services.utilities.uuid_string_factory import uuid4_string from robot_interface.models.inspection import ( Audio, Image, @@ -13,6 +12,7 @@ Video, ) from robot_interface.models.mission.status import StepStatus +from robot_interface.utilities.uuid_string_factory import uuid4_string @dataclass diff --git a/src/isar/models/mission/mission.py b/src/robot_interface/models/mission/task.py similarity index 61% rename from src/isar/models/mission/mission.py rename to src/robot_interface/models/mission/task.py index 8d8eb41d..50f735c8 100644 --- a/src/isar/models/mission/mission.py +++ b/src/robot_interface/models/mission/task.py @@ -1,21 +1,15 @@ from dataclasses import dataclass, field -from datetime import datetime -from typing import Iterator, List, Optional, Union - -from isar.apis.models.models import StartMissionResponse, TaskResponse -from isar.config.settings import settings -from isar.models.mission_metadata.mission_metadata import MissionMetadata -from isar.services.utilities.uuid_string_factory import uuid4_string -from robot_interface.models.mission import ( - STEPS, +from typing import Iterator, List, Optional + +from robot_interface.models.mission.status import StepStatus, TaskStatus +from robot_interface.models.mission.step import ( + DriveToPose, InspectionStep, MotionStep, + STEPS, Step, - StepStatus, ) -from robot_interface.models.mission.step import DriveToPose - -from .status import MissionStatus, TaskStatus +from robot_interface.utilities.uuid_string_factory import uuid4_string @dataclass @@ -100,46 +94,3 @@ def _all_inspection_steps_failed(self) -> bool: def __post_init__(self) -> None: if self._iterator is None: self._iterator = iter(self.steps) - - def api_response(self) -> TaskResponse: - return TaskResponse( - id=self.id, - tag_id=self.tag_id, - steps=list( - map(lambda x: {"id": x.id, "type": x.__class__.__name__}, self.steps) - ), - ) - - -@dataclass -class Mission: - tasks: List[Task] - id: str = field(default_factory=uuid4_string, init=True) - status: MissionStatus = MissionStatus.NotStarted - metadata: MissionMetadata = None - - def set_unique_id_and_metadata(self) -> None: - self._set_unique_id() - self.metadata = MissionMetadata(mission_id=self.id) - - def _set_unique_id(self) -> None: - plant_short_name: str = settings.PLANT_SHORT_NAME - robot_name: str = settings.ROBOT_NAME - now: datetime = datetime.utcnow() - self.id = ( - f"{plant_short_name.upper()}{robot_name.upper()}" - f"{now.strftime('%d%m%Y%H%M%S%f')[:-3]}" - ) - - def __post_init__(self) -> None: - if self.id is None: - self._set_unique_id() - - if self.metadata is None: - self.metadata = MissionMetadata(mission_id=self.id) - - def api_response(self) -> StartMissionResponse: - return StartMissionResponse( - id=self.id, - tasks=[task.api_response() for task in self.tasks], - ) diff --git a/src/robot_interface/robot_interface.py b/src/robot_interface/robot_interface.py index 87e206d0..9464c239 100644 --- a/src/robot_interface/robot_interface.py +++ b/src/robot_interface/robot_interface.py @@ -5,13 +5,34 @@ from robot_interface.models.initialize import InitializeParams from robot_interface.models.inspection.inspection import Inspection -from robot_interface.models.mission import InspectionStep, Step, StepStatus -from robot_interface.models.mission.status import RobotStatus +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.status import RobotStatus, StepStatus +from robot_interface.models.mission.step import InspectionStep, Step class RobotInterface(metaclass=ABCMeta): """Interface to communicate with robots.""" + @abstractmethod + def initiate_mission(self, mission: Mission) -> None: + """Send a mission to the robot and initiate execution of the mission + + Parameters + ---------- + mission: Mission + + Returns + ------- + None + + Raises + ------ + RobotException + If the mission is not initiated. + + """ + raise NotImplementedError + @abstractmethod def initiate_step(self, step: Step) -> None: """Send a step to the robot and initiate the execution of the step diff --git a/src/isar/services/utilities/uuid_string_factory.py b/src/robot_interface/utilities/uuid_string_factory.py similarity index 100% rename from src/isar/services/utilities/uuid_string_factory.py rename to src/robot_interface/utilities/uuid_string_factory.py diff --git a/tests/integration/turtlebot/test_successful_mission.py b/tests/integration/turtlebot/test_successful_mission.py index 742337f1..2e5887f3 100644 --- a/tests/integration/turtlebot/test_successful_mission.py +++ b/tests/integration/turtlebot/test_successful_mission.py @@ -14,7 +14,6 @@ from isar.apis.api import API from isar.config.settings import settings -from isar.models.mission import Mission from isar.modules import ( APIModule, LocalPlannerModule, @@ -28,7 +27,8 @@ ) from isar.services.readers.base_reader import BaseReader from isar.state_machine.states_enum import States -from robot_interface.models.mission import DriveToPose +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.step import DriveToPose from tests.isar.state_machine.test_state_machine import ( StateMachineThread, UploaderThread, diff --git a/tests/isar/models/test_start_mission_definition.py b/tests/isar/models/test_start_mission_definition.py index 4397088d..36136c00 100644 --- a/tests/isar/models/test_start_mission_definition.py +++ b/tests/isar/models/test_start_mission_definition.py @@ -3,8 +3,8 @@ import pytest from isar.apis.models.start_mission_definition import get_duplicate_ids -from isar.models.mission.mission import Task from robot_interface.models.mission.step import STEPS, Step +from robot_interface.models.mission.task import Task task_1: Task = Task([], tag_id=None, id="123") task_2: Task = Task([], tag_id=None, id="123") diff --git a/tests/isar/services/readers/test_base_reader.py b/tests/isar/services/readers/test_base_reader.py index f269b040..cce62ff9 100644 --- a/tests/isar/services/readers/test_base_reader.py +++ b/tests/isar/services/readers/test_base_reader.py @@ -4,9 +4,9 @@ import pytest from alitra import Pose -from isar.models.mission import Mission from isar.services.readers.base_reader import BaseReader -from robot_interface.models.mission import Step +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.step import Step from tests.mocks.mission_definition import MockMissionDefinition from tests.mocks.pose import MockPose from tests.mocks.step import MockStep diff --git a/tests/isar/services/readers/test_mission_reader.py b/tests/isar/services/readers/test_mission_reader.py index b768d46b..501f92a5 100644 --- a/tests/isar/services/readers/test_mission_reader.py +++ b/tests/isar/services/readers/test_mission_reader.py @@ -5,9 +5,15 @@ from isar.config.settings import settings from isar.mission_planner.mission_planner_interface import MissionNotFoundError -from isar.models.mission import Mission, Task -from robot_interface.models.mission import TakeThermalImage -from robot_interface.models.mission.step import DriveToPose, Step, TakeImage +from isar.models.mission_metadata.mission_metadata import MissionMetadata +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.step import ( + DriveToPose, + Step, + TakeImage, + TakeThermalImage, +) +from robot_interface.models.mission.task import Task @pytest.mark.parametrize( @@ -63,21 +69,12 @@ def test_read_mission_from_file(mission_reader) -> None: expected_tasks = [task_1, task_2, task_3, task_4] expected_mission: Mission = Mission(tasks=expected_tasks) + expected_metadata: MissionMetadata = MissionMetadata(expected_mission.id) mission: Mission = mission_reader.read_mission_from_file( Path("./tests/test_data/test_mission_working.json") ) - assert ( - expected_mission.metadata.coordinate_reference_system - == mission.metadata.coordinate_reference_system - ) - assert ( - expected_mission.metadata.vertical_reference_system - == mission.metadata.vertical_reference_system - ) - assert ( - expected_mission.metadata.data_classification - == mission.metadata.data_classification - ) + + assert expected_metadata.data_classification == MissionMetadata.data_classification for expected_task, task in zip(expected_tasks, mission.tasks): for expected_step, step in zip(expected_task.steps, task.steps): if isinstance(expected_step, DriveToPose) and isinstance(step, DriveToPose): diff --git a/tests/isar/services/service_connections/echo/test_echo_service.py b/tests/isar/services/service_connections/echo/test_echo_service.py index 743f325c..09cdabdd 100644 --- a/tests/isar/services/service_connections/echo/test_echo_service.py +++ b/tests/isar/services/service_connections/echo/test_echo_service.py @@ -4,10 +4,15 @@ from isar.mission_planner.echo_planner import EchoPlanner from isar.mission_planner.mission_planner_interface import MissionPlannerError -from isar.models.mission import Mission from isar.services.service_connections.stid.stid_service import StidService -from robot_interface.models.mission import DriveToPose, TakeImage, TakeThermalImage -from robot_interface.models.mission.step import TakeThermalVideo, TakeVideo +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.step import ( + DriveToPose, + TakeImage, + TakeThermalImage, + TakeThermalVideo, + TakeVideo, +) @pytest.mark.parametrize( diff --git a/tests/isar/state_machine/states/test_monitor.py b/tests/isar/state_machine/states/test_monitor.py index 2fea1334..fa79c9e8 100644 --- a/tests/isar/state_machine/states/test_monitor.py +++ b/tests/isar/state_machine/states/test_monitor.py @@ -1,8 +1,10 @@ import pytest -from isar.models.mission import Mission, Task from isar.state_machine.states.monitor import Monitor -from robot_interface.models.mission import Step, StepStatus, TakeImage +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.status import StepStatus +from robot_interface.models.mission.step import Step, TakeImage +from robot_interface.models.mission.task import Task from tests.mocks.step import MockStep diff --git a/tests/isar/state_machine/test_state_machine.py b/tests/isar/state_machine/test_state_machine.py index 57504cb9..d4fe1731 100644 --- a/tests/isar/state_machine/test_state_machine.py +++ b/tests/isar/state_machine/test_state_machine.py @@ -10,7 +10,7 @@ from isar.mission_planner.local_planner import LocalPlanner from isar.models.communication.queues.queues import Queues -from isar.models.mission import Mission, Task +from isar.models.mission_metadata.mission_metadata import MissionMetadata from isar.services.utilities.scheduling_utilities import SchedulingUtilities from isar.services.utilities.threaded_request import ThreadedRequest from isar.state_machine.state_machine import StateMachine, main @@ -18,8 +18,10 @@ from isar.storage.storage_interface import StorageInterface from isar.storage.uploader import Uploader from robot_interface.models.exceptions import RobotException -from robot_interface.models.mission import DriveToPose, Step, TakeImage +from robot_interface.models.mission.mission import Mission from robot_interface.models.mission.status import StepStatus +from robot_interface.models.mission.step import DriveToPose, Step, TakeImage +from robot_interface.models.mission.task import Task from robot_interface.telemetry.mqtt_client import MqttClientInterface from tests.mocks.pose import MockPose from tests.mocks.robot_interface import MockRobot @@ -90,9 +92,11 @@ def test_reset_state_machine(state_machine) -> None: def test_state_machine_transitions(injector, state_machine_thread) -> None: step: Step = DriveToPose(pose=MockPose.default_pose) mission: Mission = Mission(tasks=[Task(steps=[step])]) # type: ignore - + metadata: MissionMetadata = MissionMetadata(mission.id) scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities) - scheduling_utilities.start_mission(mission=mission, initial_pose=None) + scheduling_utilities.start_mission( + mission=mission, initial_pose=None, mission_metadata=metadata + ) time.sleep(3) expected_transitions_list = deque( @@ -116,11 +120,14 @@ def test_state_machine_failed_dependency( drive_to_step: Step = DriveToPose(pose=MockPose.default_pose) inspection_step: Step = MockStep.take_image_in_coordinate_direction mission: Mission = Mission(tasks=[Task(steps=[drive_to_step, inspection_step])]) # type: ignore + metadata: MissionMetadata = MissionMetadata(mission.id) mocker.patch.object(MockRobot, "step_status", return_value=StepStatus.Failed) scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities) - scheduling_utilities.start_mission(mission=mission, initial_pose=None) + scheduling_utilities.start_mission( + mission=mission, initial_pose=None, mission_metadata=metadata + ) time.sleep(3) expected_transitions_list = deque( @@ -146,8 +153,11 @@ def test_state_machine_with_successful_collection( step: TakeImage = MockStep.take_image_in_coordinate_direction mission: Mission = Mission(tasks=[Task(steps=[step])]) scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities) + metadata: MissionMetadata = MissionMetadata(mission.id) - scheduling_utilities.start_mission(mission=mission, initial_pose=None) + scheduling_utilities.start_mission( + mission=mission, initial_pose=None, mission_metadata=metadata + ) time.sleep(3) expected_transitions_list = deque( [ @@ -176,8 +186,11 @@ def test_state_machine_with_unsuccessful_collection( step: TakeImage = MockStep.take_image_in_coordinate_direction mission: Mission = Mission(tasks=[Task(steps=[step])]) scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities) + metadata: MissionMetadata = MissionMetadata(mission.id) - scheduling_utilities.start_mission(mission=mission, initial_pose=None) + scheduling_utilities.start_mission( + mission=mission, initial_pose=None, mission_metadata=metadata + ) time.sleep(3) expected_transitions_list = deque( [ @@ -204,8 +217,11 @@ def test_state_machine_with_successful_mission_stop( ) -> None: step: TakeImage = MockStep.take_image_in_coordinate_direction mission: Mission = Mission(tasks=[Task(steps=[step])]) + metadata: MissionMetadata = MissionMetadata(mission.id) scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities) - scheduling_utilities.start_mission(mission=mission, initial_pose=None) + scheduling_utilities.start_mission( + mission=mission, initial_pose=None, mission_metadata=metadata + ) scheduling_utilities.stop_mission() expected = deque( [ @@ -233,8 +249,11 @@ def test_state_machine_with_unsuccsessful_mission_stop( ) -> None: step: TakeImage = MockStep.take_image_in_coordinate_direction mission: Mission = Mission(tasks=[Task(steps=[step])]) + metadata: MissionMetadata = MissionMetadata(mission.id) scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities) - scheduling_utilities.start_mission(mission=mission, initial_pose=None) + scheduling_utilities.start_mission( + mission=mission, initial_pose=None, mission_metadata=metadata + ) mocker.patch.object(ThreadedRequest, "get_output", side_effect=RobotException) scheduling_utilities.stop_mission() expected = deque( diff --git a/tests/isar/storage/test_blob_storage.py b/tests/isar/storage/test_blob_storage.py index e4f96108..bbbde4ae 100644 --- a/tests/isar/storage/test_blob_storage.py +++ b/tests/isar/storage/test_blob_storage.py @@ -6,8 +6,8 @@ MISSION_ID = "some-mission-id" ARBITRARY_IMAGE_METADATA = ImageMetadata( - datetime.now(), - Pose( + start_time=datetime.now(), + pose=Pose( Position(0, 0, 0, Frame("asset")), Orientation(0, 0, 0, 1, Frame("asset")), Frame("asset"), diff --git a/tests/isar/storage/test_uploader.py b/tests/isar/storage/test_uploader.py index 09aa8a58..e17a6c34 100644 --- a/tests/isar/storage/test_uploader.py +++ b/tests/isar/storage/test_uploader.py @@ -8,17 +8,21 @@ from injector import Injector from isar.models.communication.queues.queues import Queues -from isar.models.mission import Mission from isar.models.mission_metadata.mission_metadata import MissionMetadata from isar.storage.storage_interface import StorageInterface from isar.storage.uploader import Uploader from robot_interface.models.inspection.inspection import ImageMetadata, Inspection from robot_interface.telemetry.mqtt_client import MqttClientInterface +from robot_interface.models.inspection.inspection import ( + ImageMetadata, + Inspection, +) +from robot_interface.models.mission.mission import Mission MISSION_ID = "some-mission-id" ARBITRARY_IMAGE_METADATA = ImageMetadata( - datetime.now(), - Pose( + start_time=datetime.now(), + pose=Pose( Position(0, 0, 0, Frame("asset")), Orientation(x=0, y=0, z=0, w=1, frame=Frame("asset")), Frame("asset"), @@ -49,10 +53,12 @@ def uploader_thread(injector) -> UploaderThread: def test_should_upload_from_queue(uploader_thread) -> None: mission: Mission = Mission([]) inspection: Inspection = Inspection(ARBITRARY_IMAGE_METADATA) + metadata: MissionMetadata = MissionMetadata(mission.id) message: Tuple[Inspection, MissionMetadata] = ( inspection, - mission.metadata, + metadata, ) + uploader_thread.uploader.upload_queue.put(message) time.sleep(1) assert uploader_thread.uploader.storage_handlers[0].blob_exists(inspection) @@ -61,9 +67,10 @@ def test_should_upload_from_queue(uploader_thread) -> None: def test_should_retry_failed_upload_from_queue(uploader_thread, mocker) -> None: mission: Mission = Mission([]) inspection: Inspection = Inspection(ARBITRARY_IMAGE_METADATA) + metadata: MissionMetadata = MissionMetadata(mission.id) message: Tuple[Inspection, MissionMetadata] = ( inspection, - mission.metadata, + metadata, ) # Need it to fail so that it retries diff --git a/tests/mocks/mission_definition.py b/tests/mocks/mission_definition.py index dd4b527e..3d6f1be1 100644 --- a/tests/mocks/mission_definition.py +++ b/tests/mocks/mission_definition.py @@ -12,7 +12,8 @@ StartMissionInspectionDefinition, StartMissionTaskDefinition, ) -from isar.models.mission import Mission, Task +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.task import Task from tests.mocks.step import MockStep diff --git a/tests/mocks/robot_interface.py b/tests/mocks/robot_interface.py index d02354d8..f5394815 100644 --- a/tests/mocks/robot_interface.py +++ b/tests/mocks/robot_interface.py @@ -11,8 +11,9 @@ ImageMetadata, Inspection, ) -from robot_interface.models.mission import InspectionStep, Step, StepStatus -from robot_interface.models.mission.status import RobotStatus +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.status import RobotStatus, StepStatus +from robot_interface.models.mission.step import InspectionStep, Step from robot_interface.robot_interface import RobotInterface @@ -35,6 +36,9 @@ def __init__( self.robot_pose_return_value: Pose = pose self.robot_status_return_value: RobotStatus = robot_status + def initiate_mission(self, mission: Mission) -> None: + return + def initiate_step(self, step: Step) -> None: return @@ -63,8 +67,8 @@ def robot_status(self) -> RobotStatus: def mock_image_metadata() -> ImageMetadata: return ImageMetadata( - datetime.now(), - Pose( + start_time=datetime.now(), + pose=Pose( Position(0, 0, 0, Frame("robot")), Orientation(0, 0, 0, 1, Frame("robot")), Frame("robot"), diff --git a/tests/mocks/step.py b/tests/mocks/step.py index ec8dbf46..05837f67 100644 --- a/tests/mocks/step.py +++ b/tests/mocks/step.py @@ -1,6 +1,6 @@ from alitra import Frame, Position -from robot_interface.models.mission import DriveToPose, TakeImage +from robot_interface.models.mission.step import DriveToPose, TakeImage from tests.mocks.pose import MockPose