|
1 | 1 | import logging |
2 | | -import time |
3 | 2 | from datetime import datetime, timezone |
4 | 3 | from logging import Logger |
5 | 4 | from queue import Queue |
6 | 5 | from threading import Thread |
7 | 6 | from typing import Callable, List, Optional |
8 | 7 |
|
| 8 | +from robot_interface.models.exceptions.robot_exceptions import ( |
| 9 | + RobotCommunicationException, |
| 10 | +) |
9 | 11 | from robot_interface.models.inspection.inspection import Inspection |
10 | 12 | from robot_interface.models.mission.mission import Mission |
11 | | -from robot_interface.models.mission.status import RobotStatus, TaskStatus |
| 13 | +from robot_interface.models.mission.status import MissionStatus, RobotStatus, TaskStatus |
12 | 14 | from robot_interface.models.mission.task import ( |
13 | 15 | InspectionTask, |
14 | 16 | RecordAudio, |
15 | | - ReturnToHome, |
16 | 17 | TakeCO2Measurement, |
17 | 18 | TakeImage, |
18 | 19 | TakeThermalImage, |
19 | 20 | TakeThermalVideo, |
20 | 21 | TakeVideo, |
21 | | - Task, |
22 | 22 | ) |
23 | 23 | from robot_interface.models.robots.media import MediaConfig |
24 | 24 | from robot_interface.robot_interface import RobotInterface |
25 | 25 | from robot_interface.telemetry.mqtt_client import MqttTelemetryPublisher |
26 | 26 |
|
27 | 27 | from isar_robot import inspections, telemetry |
28 | 28 | from isar_robot.config.settings import settings |
| 29 | +from isar_robot.simulation import MissionSimulation |
29 | 30 |
|
30 | 31 |
|
31 | 32 | class Robot(RobotInterface): |
32 | 33 | def __init__(self) -> None: |
33 | 34 | self.telemetry = telemetry.Telemetry() |
34 | 35 | self.logger: Logger = logging.getLogger("isar_robot") |
35 | | - self.current_mission: Optional[Mission] = None |
36 | | - self.current_task: Optional[Task] = None |
37 | 36 | self.last_task_completion_time: datetime = datetime.now(timezone.utc) |
38 | 37 | self.robot_is_home: bool = False |
| 38 | + self.mission_simulation: Optional[MissionSimulation] = None |
39 | 39 |
|
40 | 40 | def initiate_mission(self, mission: Mission) -> None: |
41 | | - time.sleep(settings.INITIATE_MISSION_DURATION_IN_SECONDS) |
42 | | - self.current_mission = mission |
43 | | - self.current_task_ix = 0 |
44 | | - self.current_task = mission.tasks[self.current_task_ix] |
45 | | - self.task_len = len(mission.tasks) |
46 | | - self.last_task_completion_time = datetime.now(timezone.utc) |
| 41 | + if self.mission_simulation and not self.mission_simulation.mission_done: |
| 42 | + raise RobotCommunicationException( |
| 43 | + error_description="Could not start mission as one is already running" |
| 44 | + ) |
| 45 | + elif self.mission_simulation: |
| 46 | + self.mission_simulation.join() |
| 47 | + self.mission_simulation = MissionSimulation(mission) |
| 48 | + self.mission_simulation.start() |
47 | 49 | self.robot_is_home = False |
48 | 50 |
|
49 | 51 | def task_status(self, task_id: str) -> TaskStatus: |
50 | | - now: datetime = datetime.now(timezone.utc) |
51 | | - if ( |
52 | | - now - self.last_task_completion_time |
53 | | - ).total_seconds() < settings.TASK_DURATION_IN_SECONDS: |
54 | | - return TaskStatus.InProgress |
55 | | - self.last_task_completion_time = now |
56 | | - |
57 | | - next_task: Task = None |
58 | | - if self.current_mission: |
59 | | - if self.current_task_ix < self.task_len - 1: |
60 | | - self.current_task_ix = self.current_task_ix + 1 |
61 | | - next_task = self.current_mission.tasks[self.current_task_ix] |
62 | | - |
63 | | - # This only happens for last task in mission |
64 | | - if isinstance(self.current_task, ReturnToHome): |
65 | | - self.current_task = None |
66 | | - if settings.SHOULD_FAIL_RETURN_TO_HOME_TASK: |
67 | | - return TaskStatus.Failed |
68 | | - self.robot_is_home = True |
69 | | - return TaskStatus.Successful |
| 52 | + if not self.mission_simulation: |
| 53 | + raise RobotCommunicationException( |
| 54 | + error_description="Could not get task status as no mission is running" |
| 55 | + ) |
70 | 56 |
|
71 | | - if next_task: |
72 | | - self.current_task = next_task |
73 | | - else: |
74 | | - self.current_task = None |
75 | | - if settings.SHOULD_FAIL_NORMAL_TASK: |
76 | | - return TaskStatus.Failed |
77 | | - return TaskStatus.Successful |
| 57 | + status = self.mission_simulation.task_status(task_id) |
| 58 | + if status == TaskStatus.Successful and self.mission_simulation.is_return_home: |
| 59 | + self.robot_is_home = True |
| 60 | + return status |
78 | 61 |
|
79 | 62 | def stop(self) -> None: |
80 | | - return |
| 63 | + if not self.mission_simulation: |
| 64 | + raise RobotCommunicationException( |
| 65 | + error_description="Attempted to stop non-existent mission" |
| 66 | + ) |
| 67 | + self.mission_simulation.stop_mission() |
81 | 68 |
|
82 | 69 | def get_inspection(self, task: InspectionTask) -> Inspection: |
83 | 70 | if type(task) in [TakeImage, TakeThermalImage]: |
@@ -174,15 +161,29 @@ def get_telemetry_publishers( |
174 | 161 | return publisher_threads |
175 | 162 |
|
176 | 163 | def robot_status(self) -> RobotStatus: |
| 164 | + if self.mission_simulation and self.mission_simulation.is_alive(): |
| 165 | + mission_status: MissionStatus = self.mission_simulation.mission_status() |
| 166 | + if mission_status == MissionStatus.Paused: |
| 167 | + return RobotStatus.Paused |
| 168 | + elif mission_status in [MissionStatus.InProgress, MissionStatus.NotStarted]: |
| 169 | + return RobotStatus.Busy |
177 | 170 | if self.robot_is_home: |
178 | 171 | return RobotStatus.Home |
179 | 172 | return RobotStatus.Available |
180 | 173 |
|
181 | 174 | def pause(self) -> None: |
182 | | - return |
| 175 | + if not self.mission_simulation: |
| 176 | + raise RobotCommunicationException( |
| 177 | + error_description="Attempted to pause non-existent mission" |
| 178 | + ) |
| 179 | + self.mission_simulation.pause_mission() |
183 | 180 |
|
184 | 181 | def resume(self) -> None: |
185 | | - return |
| 182 | + if not self.mission_simulation: |
| 183 | + raise RobotCommunicationException( |
| 184 | + error_description="Attempted to resume non-existent mission" |
| 185 | + ) |
| 186 | + self.mission_simulation.resume_mission() |
186 | 187 |
|
187 | 188 | def generate_media_config(self) -> Optional[MediaConfig]: |
188 | 189 | return None |
|
0 commit comments