isar 1.34.9__py3-none-any.whl → 1.34.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. isar/apis/api.py +0 -23
  2. isar/apis/models/start_mission_definition.py +6 -3
  3. isar/apis/schedule/scheduling_controller.py +3 -29
  4. isar/config/keyvault/keyvault_service.py +3 -1
  5. isar/config/settings.py +0 -8
  6. isar/models/status.py +4 -0
  7. isar/modules.py +0 -2
  8. isar/robot/robot.py +8 -2
  9. isar/script.py +1 -1
  10. isar/services/utilities/scheduling_utilities.py +0 -42
  11. isar/state_machine/state_machine.py +24 -2
  12. isar/state_machine/states/maintenance.py +8 -1
  13. isar/state_machine/states/stopping.py +8 -0
  14. isar/state_machine/states/stopping_paused_mission.py +36 -0
  15. isar/state_machine/states/stopping_paused_return_home.py +59 -0
  16. isar/state_machine/states/stopping_return_home.py +24 -42
  17. isar/state_machine/states/unknown_status.py +2 -0
  18. isar/state_machine/states_enum.py +2 -0
  19. isar/state_machine/transitions/mission.py +37 -4
  20. isar/state_machine/transitions/return_home.py +2 -0
  21. isar/state_machine/transitions/robot_status.py +7 -0
  22. isar/state_machine/utils/common_event_handlers.py +65 -0
  23. {isar-1.34.9.dist-info → isar-1.34.13.dist-info}/METADATA +49 -16
  24. {isar-1.34.9.dist-info → isar-1.34.13.dist-info}/RECORD +29 -44
  25. robot_interface/telemetry/payloads.py +1 -1
  26. isar/config/configuration_error.py +0 -2
  27. isar/config/keyvault/keyvault_error.py +0 -2
  28. isar/config/predefined_mission_definition/__init__.py +0 -0
  29. isar/config/predefined_mission_definition/default_exr.json +0 -49
  30. isar/config/predefined_mission_definition/default_mission.json +0 -87
  31. isar/config/predefined_mission_definition/default_turtlebot.json +0 -117
  32. isar/config/predefined_missions/__init__.py +0 -0
  33. isar/config/predefined_missions/default.json +0 -72
  34. isar/config/predefined_missions/default_extra_capabilities.json +0 -107
  35. isar/mission_planner/__init__.py +0 -0
  36. isar/mission_planner/local_planner.py +0 -68
  37. isar/mission_planner/mission_planner_interface.py +0 -26
  38. isar/services/auth/__init__.py +0 -0
  39. isar/services/auth/azure_credentials.py +0 -14
  40. isar/services/service_connections/request_handler.py +0 -153
  41. isar/services/utilities/threaded_request.py +0 -68
  42. robot_interface/models/initialize/__init__.py +0 -0
  43. {isar-1.34.9.dist-info → isar-1.34.13.dist-info}/WHEEL +0 -0
  44. {isar-1.34.9.dist-info → isar-1.34.13.dist-info}/entry_points.txt +0 -0
  45. {isar-1.34.9.dist-info → isar-1.34.13.dist-info}/licenses/LICENSE +0 -0
  46. {isar-1.34.9.dist-info → isar-1.34.13.dist-info}/top_level.txt +0 -0
isar/apis/api.py CHANGED
@@ -112,29 +112,6 @@ class API:
112
112
 
113
113
  authentication_dependency: Security = Security(self.authenticator.get_scheme())
114
114
 
115
- router.add_api_route(
116
- path="/schedule/start-mission/{id}",
117
- endpoint=self.scheduling_controller.start_mission_by_id,
118
- methods=["POST"],
119
- deprecated=True,
120
- dependencies=[authentication_dependency],
121
- summary="Start a mission with id='id' from the current mission planner",
122
- responses={
123
- HTTPStatus.OK.value: {
124
- "description": "Mission succesfully started",
125
- "model": StartMissionResponse,
126
- },
127
- HTTPStatus.NOT_FOUND.value: {
128
- "description": "Not found - Mission not found",
129
- },
130
- HTTPStatus.CONFLICT.value: {
131
- "description": "Conflict - Invalid command in the current state",
132
- },
133
- HTTPStatus.INTERNAL_SERVER_ERROR.value: {
134
- "description": "Internal Server Error - Current state of state machine unknown",
135
- },
136
- },
137
- )
138
115
  router.add_api_route(
139
116
  path="/schedule/start-mission",
140
117
  endpoint=self.scheduling_controller.start_mission,
@@ -6,7 +6,6 @@ from pydantic import BaseModel, Field
6
6
 
7
7
  from isar.apis.models.models import InputPose, InputPosition
8
8
  from isar.config.settings import settings
9
- from isar.mission_planner.mission_planner_interface import MissionPlannerError
10
9
  from robot_interface.models.mission.mission import Mission
11
10
  from robot_interface.models.mission.task import (
12
11
  TASKS,
@@ -63,6 +62,10 @@ class StopMissionDefinition(BaseModel):
63
62
  mission_id: Optional[str] = None
64
63
 
65
64
 
65
+ class MissionFormatError(Exception):
66
+ pass
67
+
68
+
66
69
  def to_isar_mission(
67
70
  start_mission_definition: StartMissionDefinition,
68
71
  ) -> Mission:
@@ -73,7 +76,7 @@ def to_isar_mission(
73
76
  isar_tasks.append(task)
74
77
 
75
78
  if not isar_tasks:
76
- raise MissionPlannerError("Mission does not contain any valid tasks")
79
+ raise MissionFormatError("Mission does not contain any valid tasks")
77
80
 
78
81
  isar_mission_name: str = (
79
82
  start_mission_definition.name
@@ -101,7 +104,7 @@ def to_isar_task(task_definition: StartMissionTaskDefinition) -> TASKS:
101
104
  elif task_definition.type == TaskType.ReturnToHome:
102
105
  return ReturnToHome()
103
106
  else:
104
- raise MissionPlannerError(
107
+ raise MissionFormatError(
105
108
  f"Failed to create task: '{task_definition.type}' is not a valid"
106
109
  )
107
110
 
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  from http import HTTPStatus
3
3
 
4
- from fastapi import Body, HTTPException, Path
4
+ from fastapi import Body, HTTPException
5
5
  from opentelemetry import trace
6
6
 
7
7
  from isar.apis.models.models import (
@@ -10,12 +10,12 @@ from isar.apis.models.models import (
10
10
  TaskResponse,
11
11
  )
12
12
  from isar.apis.models.start_mission_definition import (
13
+ MissionFormatError,
13
14
  StartMissionDefinition,
14
15
  StopMissionDefinition,
15
16
  to_isar_mission,
16
17
  )
17
18
  from isar.config.settings import robot_settings
18
- from isar.mission_planner.mission_planner_interface import MissionPlannerError
19
19
  from isar.services.utilities.scheduling_utilities import SchedulingUtilities
20
20
  from isar.state_machine.states_enum import States
21
21
  from robot_interface.models.mission.mission import Mission
@@ -32,32 +32,6 @@ class SchedulingController:
32
32
  self.scheduling_utilities: SchedulingUtilities = scheduling_utilities
33
33
  self.logger = logging.getLogger("api")
34
34
 
35
- @tracer.start_as_current_span("start_mission_by_id")
36
- def start_mission_by_id(
37
- self,
38
- mission_id: str = Path(
39
- alias="id",
40
- title="Mission ID",
41
- description="ID-number for predefined mission",
42
- ),
43
- ) -> StartMissionResponse:
44
- self.logger.info("Received request to start mission with id %s", mission_id)
45
-
46
- state: States = self.scheduling_utilities.get_state()
47
- self.scheduling_utilities.verify_state_machine_ready_to_receive_mission(state)
48
-
49
- mission: Mission = self.scheduling_utilities.get_mission(mission_id)
50
-
51
- self.scheduling_utilities.verify_robot_capable_of_mission(
52
- mission=mission, robot_capabilities=robot_settings.CAPABILITIES
53
- )
54
-
55
- self.logger.info("Starting mission with ISAR Mission ID: '%s'", mission.id)
56
-
57
- self.scheduling_utilities.start_mission(mission=mission)
58
-
59
- return self._api_response(mission)
60
-
61
35
  @tracer.start_as_current_span("start_mission")
62
36
  def start_mission(
63
37
  self,
@@ -87,7 +61,7 @@ class SchedulingController:
87
61
  mission: Mission = to_isar_mission(
88
62
  start_mission_definition=mission_definition
89
63
  )
90
- except MissionPlannerError as e:
64
+ except MissionFormatError as e:
91
65
  error_message = f"Bad Request - Cannot create ISAR mission: {e}"
92
66
  self.logger.warning(error_message)
93
67
  raise HTTPException(
@@ -9,7 +9,9 @@ from azure.core.exceptions import (
9
9
  from azure.identity import ClientSecretCredential, DefaultAzureCredential
10
10
  from azure.keyvault.secrets import KeyVaultSecret, SecretClient
11
11
 
12
- from isar.config.keyvault.keyvault_error import KeyvaultError
12
+
13
+ class KeyvaultError(Exception):
14
+ pass
13
15
 
14
16
 
15
17
  class Keyvault:
isar/config/settings.py CHANGED
@@ -6,7 +6,6 @@ from dotenv import load_dotenv
6
6
  from pydantic import Field, ValidationInfo, field_validator
7
7
  from pydantic_settings import BaseSettings, SettingsConfigDict
8
8
 
9
- from isar.config import predefined_missions
10
9
  from robot_interface.models.robots.robot_model import RobotModel
11
10
  from robot_interface.telemetry.payloads import DocumentInfo
12
11
 
@@ -36,10 +35,6 @@ class Settings(BaseSettings):
36
35
  # The sleep is used to throttle the system on every iteration in the loop
37
36
  FSM_SLEEP_TIME: float = Field(default=0.1)
38
37
 
39
- # Location of JSON files containing predefined missions for the Local Planner to use
40
- path: str = os.path.dirname(predefined_missions.__file__)
41
- PREDEFINED_MISSIONS_FOLDER: str = Field(default=path + "/")
42
-
43
38
  # Name of default map transformation
44
39
  DEFAULT_MAP: str = Field(default="default_map")
45
40
 
@@ -93,9 +88,6 @@ class Settings(BaseSettings):
93
88
  # Sets how many times the robot should try to return home if a return home fails
94
89
  RETURN_HOME_RETRY_LIMIT: int = Field(default=5)
95
90
 
96
- # Determines which mission planner module is used by ISAR
97
- MISSION_PLANNER: str = Field(default="local")
98
-
99
91
  # Determines which storage modules are used by ISAR
100
92
  # Multiple storage modules can be chosen
101
93
  # Each module will be called when storing results from inspections
isar/models/status.py CHANGED
@@ -16,3 +16,7 @@ class IsarStatus(Enum):
16
16
  GoingToLockdown = "goingtolockdown"
17
17
  GoingToRecharging = "goingtorecharging"
18
18
  Maintenance = "maintenance"
19
+ Pausing = "pausing"
20
+ PausingReturnHome = "pausingreturnhome"
21
+ Stopping = "stopping"
22
+ StoppingReturnHome = "stoppingreturnhome"
isar/modules.py CHANGED
@@ -9,7 +9,6 @@ from isar.apis.schedule.scheduling_controller import SchedulingController
9
9
  from isar.apis.security.authentication import Authenticator
10
10
  from isar.config.keyvault.keyvault_service import Keyvault
11
11
  from isar.config.settings import settings
12
- from isar.mission_planner.local_planner import LocalPlanner
13
12
  from isar.models.events import Events, SharedState
14
13
  from isar.robot.robot import Robot
15
14
  from isar.services.utilities.robot_utilities import RobotUtilities
@@ -59,7 +58,6 @@ class ApplicationContainer(containers.DeclarativeContainer):
59
58
  SchedulingUtilities,
60
59
  events=events,
61
60
  shared_state=shared_state,
62
- mission_planner=providers.Singleton(LocalPlanner),
63
61
  )
64
62
  scheduling_controller = providers.Singleton(
65
63
  SchedulingController, scheduling_utilities=scheduling_utilities
isar/robot/robot.py CHANGED
@@ -142,17 +142,23 @@ class Robot(object):
142
142
  ):
143
143
  self.stop_mission_thread.join()
144
144
  error_message = self.stop_mission_thread.error_message
145
- self.stop_mission_thread = None
146
145
 
147
146
  if error_message:
148
147
  self.robot_service_events.mission_failed_to_stop.trigger_event(
149
148
  error_message
150
149
  )
150
+ self.stop_mission_thread = None
151
151
  else:
152
- self.signal_mission_stopped.set()
153
152
  if self.monitor_mission_thread is not None:
153
+ if self.monitor_mission_thread.is_alive():
154
+ self.signal_mission_stopped.set()
155
+ return
154
156
  self.monitor_mission_thread.join()
155
157
  self.monitor_mission_thread = None
158
+
159
+ self.stop_mission_thread = None
160
+ # The mission status will already be reported on MQTT, the state machine does not need the event
161
+ self.robot_service_events.mission_status_updated.clear_event()
156
162
  self.robot_service_events.mission_successfully_stopped.trigger_event(
157
163
  True
158
164
  )
isar/script.py CHANGED
@@ -58,10 +58,10 @@ def print_startup_info():
58
58
  )
59
59
 
60
60
  print_setting("ISAR settings")
61
+ print_setting("ISAR ID", settings.ISAR_ID)
61
62
  print_setting("Robot package", settings.ROBOT_PACKAGE)
62
63
  print_setting("Robot name", settings.ROBOT_NAME)
63
64
  print_setting("Running on port", settings.API_PORT)
64
- print_setting("Mission planner", settings.MISSION_PLANNER)
65
65
  print_setting("Using local storage", settings.STORAGE_LOCAL_ENABLED)
66
66
  print_setting("Using blob storage", settings.STORAGE_BLOB_ENABLED)
67
67
  print_setting("Blob storage account", settings.BLOB_STORAGE_ACCOUNT)
@@ -4,15 +4,9 @@ from http import HTTPStatus
4
4
  from typing import List, TypeVar
5
5
 
6
6
  from fastapi import HTTPException
7
- from requests import HTTPError
8
7
 
9
8
  from isar.apis.models.models import ControlMissionResponse, MaintenanceResponse
10
9
  from isar.config.settings import settings
11
- from isar.mission_planner.mission_planner_interface import (
12
- MissionNotFoundError,
13
- MissionPlannerError,
14
- MissionPlannerInterface,
15
- )
16
10
  from isar.models.events import (
17
11
  APIEvent,
18
12
  APIRequests,
@@ -41,12 +35,10 @@ class SchedulingUtilities:
41
35
  self,
42
36
  events: Events,
43
37
  shared_state: SharedState,
44
- mission_planner: MissionPlannerInterface,
45
38
  queue_timeout: int = settings.QUEUE_TIMEOUT,
46
39
  ):
47
40
  self.api_events: APIRequests = events.api_requests
48
41
  self.shared_state: SharedState = shared_state
49
- self.mission_planner: MissionPlannerInterface = mission_planner
50
42
  self.queue_timeout: int = queue_timeout
51
43
  self.logger = logging.getLogger("api")
52
44
 
@@ -69,40 +61,6 @@ class SchedulingUtilities:
69
61
  )
70
62
  return current_state
71
63
 
72
- def get_mission(self, mission_id: str) -> Mission:
73
- """Get the mission with mission_id from the current mission planner
74
-
75
- Raises
76
- ------
77
- HTTPException 404 Not Found
78
- If requested mission with mission_id is not found
79
- HTTPException 500 Internal Server Error
80
- If for some reason the mission can not be returned
81
- """
82
- try:
83
- return self.mission_planner.get_mission(mission_id)
84
- except HTTPError as e:
85
- self.logger.error(e)
86
- raise HTTPException(status_code=e.response.status_code)
87
- except MissionNotFoundError as e:
88
- self.logger.error(e)
89
- raise HTTPException(
90
- status_code=HTTPStatus.NOT_FOUND,
91
- detail=f"Mission with id '{mission_id}' not found",
92
- )
93
- except MissionPlannerError as e:
94
- self.logger.error(e)
95
- raise HTTPException(
96
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
97
- detail="Could not plan mission",
98
- )
99
- except Exception as e:
100
- self.logger.error(e)
101
- raise HTTPException(
102
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
103
- detail="Could not return mission",
104
- )
105
-
106
64
  def verify_robot_capable_of_mission(
107
65
  self, mission: Mission, robot_capabilities: List[str]
108
66
  ) -> bool:
@@ -41,6 +41,10 @@ from isar.state_machine.states.stopping_due_to_maintenance import (
41
41
  )
42
42
  from isar.state_machine.states.stopping_go_to_lockdown import StoppingGoToLockdown
43
43
  from isar.state_machine.states.stopping_go_to_recharge import StoppingGoToRecharge
44
+ from isar.state_machine.states.stopping_paused_mission import StoppingPausedMission
45
+ from isar.state_machine.states.stopping_paused_return_home import (
46
+ StoppingPausedReturnHome,
47
+ )
44
48
  from isar.state_machine.states.stopping_return_home import StoppingReturnHome
45
49
  from isar.state_machine.states.unknown_status import UnknownStatus
46
50
  from isar.state_machine.states_enum import States
@@ -106,6 +110,8 @@ class StateMachine(object):
106
110
  self.going_to_lockdown_state: State = GoingToLockdown(self)
107
111
  self.going_to_recharging_state: State = GoingToRecharging(self)
108
112
  self.stopping_due_to_maintenance_state: State = StoppingDueToMaintenance(self)
113
+ self.stopping_paused_mission_state: State = StoppingPausedMission(self)
114
+ self.stopping_paused_return_home_state: State = StoppingPausedReturnHome(self)
109
115
 
110
116
  # States Waiting for mission
111
117
  self.await_next_mission_state: State = AwaitNextMission(self)
@@ -147,6 +153,8 @@ class StateMachine(object):
147
153
  self.stopping_go_to_recharge_state,
148
154
  self.stopping_due_to_maintenance_state,
149
155
  self.maintenance_state,
156
+ self.stopping_paused_mission_state,
157
+ self.stopping_paused_return_home_state,
150
158
  ]
151
159
 
152
160
  if settings.PERSISTENT_STORAGE_CONNECTION_STRING == "":
@@ -237,9 +245,8 @@ class StateMachine(object):
237
245
 
238
246
  if self.shared_state.mission_id.check() is None:
239
247
  self.logger.warning(
240
- "Could not publish mission aborted message. No ongoing mission."
248
+ "Publishing mission aborted message with no ongoing mission."
241
249
  )
242
- return
243
250
 
244
251
  payload: MissionAbortedPayload = MissionAbortedPayload(
245
252
  isar_id=settings.ISAR_ID,
@@ -309,6 +316,21 @@ class StateMachine(object):
309
316
  return IsarStatus.GoingToRecharging
310
317
  elif self.current_state == States.Maintenance:
311
318
  return IsarStatus.Maintenance
319
+ elif self.current_state == States.Pausing:
320
+ return IsarStatus.Pausing
321
+ elif self.current_state == States.PausingReturnHome:
322
+ return IsarStatus.PausingReturnHome
323
+ elif self.current_state in [
324
+ States.Stopping,
325
+ States.StoppingDueToMaintenance,
326
+ States.StoppingGoToLockdown,
327
+ States.StoppingGoToRecharge,
328
+ States.StoppingPausedMission,
329
+ States.StoppingPausedReturnHome,
330
+ ]:
331
+ return IsarStatus.Stopping
332
+ elif self.current_state == States.StoppingReturnHome:
333
+ return IsarStatus.StoppingReturnHome
312
334
  else:
313
335
  return IsarStatus.Busy
314
336
 
@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, List
2
2
 
3
3
  from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
4
4
  from isar.models.events import Event
5
+ from robot_interface.models.mission.status import RobotStatus
5
6
 
6
7
  if TYPE_CHECKING:
7
8
  from isar.state_machine.state_machine import StateMachine
@@ -18,7 +19,13 @@ class Maintenance(EventHandlerBase):
18
19
  events.api_requests.release_from_maintenance_mode.response.trigger_event(
19
20
  True
20
21
  )
21
- return state_machine.release_from_maintenance # type: ignore
22
+
23
+ robot_status = state_machine.shared_state.robot_status.check()
24
+ if robot_status == RobotStatus.Home:
25
+ return state_machine.goto_home # type: ignore
26
+ else:
27
+ return state_machine.goto_intervention_needed # type: ignore
28
+
22
29
  return None
23
30
 
24
31
  event_handlers: List[EventHandlerMapping] = [
@@ -36,6 +36,14 @@ class Stopping(EventHandlerBase):
36
36
  state_machine.events.api_requests.stop_mission.response.trigger_event(
37
37
  ControlMissionResponse(success=True)
38
38
  )
39
+
40
+ if state_machine.shared_state.mission_id.check() is None:
41
+ reason: str = (
42
+ "Robot was busy and mission stopped but no ongoing mission found in shared state."
43
+ )
44
+ state_machine.logger.warning(reason)
45
+ state_machine.publish_mission_aborted(reason, False)
46
+
39
47
  state_machine.print_transitions()
40
48
  if not state_machine.battery_level_is_above_mission_start_threshold():
41
49
  state_machine.start_return_home_mission()
@@ -0,0 +1,36 @@
1
+ from typing import TYPE_CHECKING, List
2
+
3
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
4
+ from isar.state_machine.utils.common_event_handlers import (
5
+ failed_stop_event_handler,
6
+ successful_stop_event_handler,
7
+ )
8
+
9
+ if TYPE_CHECKING:
10
+ from isar.state_machine.state_machine import StateMachine
11
+
12
+
13
+ class StoppingPausedMission(EventHandlerBase):
14
+
15
+ def __init__(self, state_machine: "StateMachine"):
16
+ events = state_machine.events
17
+
18
+ event_handlers: List[EventHandlerMapping] = [
19
+ EventHandlerMapping(
20
+ name="failed_stop_event",
21
+ event=events.robot_service_events.mission_failed_to_stop,
22
+ handler=lambda event: failed_stop_event_handler(state_machine, event),
23
+ ),
24
+ EventHandlerMapping(
25
+ name="successful_stop_event",
26
+ event=events.robot_service_events.mission_successfully_stopped,
27
+ handler=lambda event: successful_stop_event_handler(
28
+ state_machine, event
29
+ ),
30
+ ),
31
+ ]
32
+ super().__init__(
33
+ state_name="stopping_paused_mission",
34
+ state_machine=state_machine,
35
+ event_handler_mappings=event_handlers,
36
+ )
@@ -0,0 +1,59 @@
1
+ from typing import TYPE_CHECKING, List, Optional
2
+
3
+ from isar.apis.models.models import MissionStartResponse
4
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
5
+ from isar.state_machine.utils.common_event_handlers import (
6
+ failed_stop_return_home_event_handler,
7
+ successful_stop_return_home_event_handler,
8
+ )
9
+ from robot_interface.models.mission.mission import Mission
10
+
11
+ if TYPE_CHECKING:
12
+ from isar.state_machine.state_machine import StateMachine
13
+
14
+
15
+ class StoppingPausedReturnHome(EventHandlerBase):
16
+
17
+ def __init__(self, state_machine: "StateMachine"):
18
+ events = state_machine.events
19
+ self.mission: Optional[Mission] = None
20
+
21
+ def _respond_to_start_mission_request():
22
+ self.mission = (
23
+ state_machine.events.api_requests.start_mission.request.consume_event()
24
+ )
25
+ if not self.mission:
26
+ state_machine.logger.error(
27
+ "Reached stopping paused return home without a mission request"
28
+ )
29
+ else:
30
+ response = MissionStartResponse(
31
+ mission_id=self.mission.id,
32
+ mission_started=True,
33
+ )
34
+ state_machine.events.api_requests.start_mission.response.trigger_event(
35
+ response
36
+ )
37
+
38
+ event_handlers: List[EventHandlerMapping] = [
39
+ EventHandlerMapping(
40
+ name="failed_stop_event",
41
+ event=events.robot_service_events.mission_failed_to_stop,
42
+ handler=lambda event: failed_stop_return_home_event_handler(
43
+ state_machine, event
44
+ ),
45
+ ),
46
+ EventHandlerMapping(
47
+ name="successful_stop_event",
48
+ event=events.robot_service_events.mission_successfully_stopped,
49
+ handler=lambda event: successful_stop_return_home_event_handler(
50
+ state_machine, event, self.mission
51
+ ),
52
+ ),
53
+ ]
54
+ super().__init__(
55
+ state_name="stopping_paused_return_home",
56
+ state_machine=state_machine,
57
+ event_handler_mappings=event_handlers,
58
+ on_entry=_respond_to_start_mission_request,
59
+ )
@@ -1,10 +1,11 @@
1
- import logging
2
- from typing import TYPE_CHECKING, Callable, List, Optional
1
+ from typing import TYPE_CHECKING, List, Optional
3
2
 
4
3
  from isar.apis.models.models import MissionStartResponse
5
4
  from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
6
- from isar.models.events import Event
7
- from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
5
+ from isar.state_machine.utils.common_event_handlers import (
6
+ failed_stop_return_home_event_handler,
7
+ successful_stop_return_home_event_handler,
8
+ )
8
9
  from robot_interface.models.mission.mission import Mission
9
10
 
10
11
  if TYPE_CHECKING:
@@ -14,64 +15,45 @@ if TYPE_CHECKING:
14
15
  class StoppingReturnHome(EventHandlerBase):
15
16
 
16
17
  def __init__(self, state_machine: "StateMachine"):
17
- logger = logging.getLogger("state_machine")
18
18
  events = state_machine.events
19
+ self.mission: Optional[Mission] = None
19
20
 
20
- def _failed_stop_event_handler(
21
- event: Event[ErrorMessage],
22
- ) -> Optional[Callable]:
23
- error_message: Optional[ErrorMessage] = event.consume_event()
24
- if error_message is None:
25
- return None
26
-
27
- logger.warning(error_message.error_description)
28
- mission: Mission = (
21
+ def _respond_to_start_mission_request():
22
+ self.mission = (
29
23
  state_machine.events.api_requests.start_mission.request.consume_event()
30
24
  )
31
- state_machine.events.api_requests.start_mission.response.trigger_event(
32
- MissionStartResponse(
33
- mission_id=mission.id,
34
- mission_started=False,
35
- mission_not_started_reason="Failed to cancel return home mission",
25
+ if not self.mission:
26
+ state_machine.logger.error(
27
+ "Reached stopping return home without a mission request"
28
+ )
29
+ else:
30
+ response = MissionStartResponse(
31
+ mission_id=self.mission.id,
32
+ mission_started=True,
36
33
  )
37
- )
38
- return state_machine.return_home_mission_stopping_failed # type: ignore
39
-
40
- def _successful_stop_event_handler(event: Event[bool]) -> Optional[Callable]:
41
- if not event.consume_event():
42
- return None
43
-
44
- mission: Mission = (
45
- state_machine.events.api_requests.start_mission.request.consume_event()
46
- )
47
-
48
- if mission:
49
- state_machine.start_mission(mission=mission)
50
34
  state_machine.events.api_requests.start_mission.response.trigger_event(
51
- MissionStartResponse(mission_started=True)
35
+ response
52
36
  )
53
- return state_machine.start_mission_monitoring # type: ignore
54
-
55
- state_machine.logger.error(
56
- "Stopped return home without a new mission to start"
57
- )
58
- state_machine.start_return_home_mission()
59
- return state_machine.start_return_home_monitoring # type: ignore
60
37
 
61
38
  event_handlers: List[EventHandlerMapping] = [
62
39
  EventHandlerMapping(
63
40
  name="failed_stop_event",
64
41
  event=events.robot_service_events.mission_failed_to_stop,
65
- handler=_failed_stop_event_handler,
42
+ handler=lambda event: failed_stop_return_home_event_handler(
43
+ state_machine, event
44
+ ),
66
45
  ),
67
46
  EventHandlerMapping(
68
47
  name="successful_stop_event",
69
48
  event=events.robot_service_events.mission_successfully_stopped,
70
- handler=_successful_stop_event_handler,
49
+ handler=lambda event: successful_stop_return_home_event_handler(
50
+ state_machine, event, self.mission
51
+ ),
71
52
  ),
72
53
  ]
73
54
  super().__init__(
74
55
  state_name="stopping_return_home",
75
56
  state_machine=state_machine,
76
57
  event_handler_mappings=event_handlers,
58
+ on_entry=_respond_to_start_mission_request,
77
59
  )
@@ -41,6 +41,8 @@ class UnknownStatus(EventHandlerBase):
41
41
  return state_machine.robot_status_offline # type: ignore
42
42
  elif robot_status == RobotStatus.BlockedProtectiveStop:
43
43
  return state_machine.robot_status_blocked_protective_stop # type: ignore
44
+ elif robot_status == RobotStatus.Busy:
45
+ return state_machine.robot_status_busy # type: ignore
44
46
  return None
45
47
 
46
48
  def _reset_status_check():
@@ -26,6 +26,8 @@ class States(str, Enum):
26
26
  StoppingGoToRecharge = "stopping_go_to_recharge"
27
27
  Maintenance = "maintenance"
28
28
  StoppingDueToMaintenance = "stopping_due_to_maintenance"
29
+ StoppingPausedMission = "stopping_paused_mission"
30
+ StoppingPausedReturnHome = "stopping_paused_return_home"
29
31
 
30
32
  def __repr__(self):
31
33
  return self.value