isar 1.26.3__py3-none-any.whl → 1.27.0__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.

Potentially problematic release.


This version of isar might be problematic. Click here for more details.

Files changed (44) hide show
  1. isar/apis/api.py +25 -0
  2. isar/apis/models/start_mission_definition.py +1 -7
  3. isar/apis/schedule/scheduling_controller.py +17 -1
  4. isar/config/settings.py +4 -1
  5. isar/models/communication/queues/events.py +1 -0
  6. isar/models/communication/queues/queue_utils.py +6 -0
  7. isar/modules.py +13 -6
  8. isar/robot/robot.py +10 -1
  9. isar/robot/robot_status.py +11 -5
  10. isar/services/utilities/scheduling_utilities.py +60 -7
  11. isar/state_machine/state_machine.py +51 -121
  12. isar/state_machine/states/await_next_mission.py +92 -0
  13. isar/state_machine/states/blocked_protective_stop.py +2 -5
  14. isar/state_machine/states/home.py +87 -0
  15. isar/state_machine/states/monitor.py +8 -3
  16. isar/state_machine/states/offline.py +3 -5
  17. isar/state_machine/states/returning_home.py +186 -0
  18. isar/state_machine/states/{idle.py → robot_standing_still.py} +20 -8
  19. isar/state_machine/states/{stop.py → stopping.py} +16 -4
  20. isar/state_machine/states/unknown_status.py +74 -0
  21. isar/state_machine/states_enum.py +6 -3
  22. isar/state_machine/transitions/functions/return_home.py +38 -0
  23. isar/state_machine/transitions/functions/robot_status.py +27 -0
  24. isar/state_machine/transitions/{start_mission.py → functions/start_mission.py} +1 -1
  25. isar/state_machine/transitions/mission.py +119 -0
  26. isar/state_machine/transitions/return_home.py +69 -0
  27. isar/state_machine/transitions/robot_status.py +73 -0
  28. {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/METADATA +14 -7
  29. {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/RECORD +43 -35
  30. {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/WHEEL +1 -1
  31. robot_interface/models/exceptions/robot_exceptions.py +14 -0
  32. robot_interface/models/mission/mission.py +8 -1
  33. robot_interface/models/mission/status.py +2 -0
  34. robot_interface/models/mission/task.py +0 -1
  35. isar/state_machine/states/off.py +0 -18
  36. /isar/state_machine/transitions/{fail_mission.py → functions/fail_mission.py} +0 -0
  37. /isar/state_machine/transitions/{finish_mission.py → functions/finish_mission.py} +0 -0
  38. /isar/state_machine/transitions/{pause.py → functions/pause.py} +0 -0
  39. /isar/state_machine/transitions/{resume.py → functions/resume.py} +0 -0
  40. /isar/state_machine/transitions/{stop.py → functions/stop.py} +0 -0
  41. /isar/state_machine/transitions/{utils.py → functions/utils.py} +0 -0
  42. {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/entry_points.txt +0 -0
  43. {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/licenses/LICENSE +0 -0
  44. {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/top_level.txt +0 -0
isar/apis/api.py CHANGED
@@ -150,6 +150,31 @@ class API:
150
150
  HTTPStatus.CONFLICT.value: {
151
151
  "description": "Conflict - Invalid command in the current state",
152
152
  },
153
+ HTTPStatus.BAD_REQUEST.value: {
154
+ "description": "Bad request - Robot does not have the capabilities to perform the mission",
155
+ },
156
+ HTTPStatus.INTERNAL_SERVER_ERROR.value: {
157
+ "description": "Internal Server Error - Current state of state machine unknown",
158
+ },
159
+ },
160
+ )
161
+ router.add_api_route(
162
+ path="/schedule/return-home",
163
+ endpoint=self.scheduling_controller.return_home,
164
+ methods=["POST"],
165
+ dependencies=[authentication_dependency],
166
+ summary="Start return home mission",
167
+ responses={
168
+ HTTPStatus.OK.value: {
169
+ "description": "Return home mission succesfully started",
170
+ "model": StartMissionResponse,
171
+ },
172
+ HTTPStatus.UNPROCESSABLE_ENTITY.value: {
173
+ "description": "Invalid body - The JSON is incorrect",
174
+ },
175
+ HTTPStatus.CONFLICT.value: {
176
+ "description": "Conflict - Invalid command in the current state",
177
+ },
153
178
  HTTPStatus.INTERNAL_SERVER_ERROR.value: {
154
179
  "description": "Internal Server Error - Current state of state machine unknown",
155
180
  },
@@ -92,7 +92,7 @@ def to_isar_task(task_definition: StartMissionTaskDefinition) -> TASKS:
92
92
  if task_definition.type == TaskType.Inspection:
93
93
  return to_inspection_task(task_definition)
94
94
  elif task_definition.type == TaskType.ReturnToHome:
95
- return create_return_to_home_task(task_definition)
95
+ return ReturnToHome()
96
96
  else:
97
97
  raise MissionPlannerError(
98
98
  f"Failed to create task: '{task_definition.type}' is not a valid"
@@ -162,11 +162,5 @@ def to_inspection_task(task_definition: StartMissionTaskDefinition) -> TASKS:
162
162
  )
163
163
 
164
164
 
165
- def create_return_to_home_task(
166
- task_definition: StartMissionTaskDefinition,
167
- ) -> ReturnToHome:
168
- return ReturnToHome(pose=task_definition.pose.to_alitra_pose())
169
-
170
-
171
165
  def _build_mission_name() -> str:
172
166
  return f"{settings.PLANT_SHORT_NAME}{settings.ROBOT_NAME}{int(time.time())}"
@@ -98,6 +98,16 @@ class SchedulingController:
98
98
  self.scheduling_utilities.start_mission(mission=mission)
99
99
  return self._api_response(mission)
100
100
 
101
+ def return_home(self) -> None:
102
+ self.logger.info("Received request to return home")
103
+
104
+ state: States = self.scheduling_utilities.get_state()
105
+ self.scheduling_utilities.verify_state_machine_ready_to_receive_return_home_mission(
106
+ state
107
+ )
108
+
109
+ self.scheduling_utilities.return_home()
110
+
101
111
  def pause_mission(self) -> ControlMissionResponse:
102
112
  self.logger.info("Received request to pause current mission")
103
113
 
@@ -142,7 +152,13 @@ class SchedulingController:
142
152
 
143
153
  state: States = self.scheduling_utilities.get_state()
144
154
 
145
- if state == States.Off:
155
+ if (
156
+ state == States.UnknownStatus
157
+ or state == States.Stopping
158
+ or state == States.BlockedProtectiveStop
159
+ or state == States.Offline
160
+ or state == States.Home
161
+ ):
146
162
  error_message = (
147
163
  f"Conflict - Stop command received in invalid state - State: {state}"
148
164
  )
isar/config/settings.py CHANGED
@@ -82,6 +82,9 @@ class Settings(BaseSettings):
82
82
  # FastAPI port
83
83
  API_PORT: int = Field(default=3000)
84
84
 
85
+ # Determines how long delay time should be allowed before returning home
86
+ RETURN_HOME_DELAY: int = Field(default=10)
87
+
85
88
  # Determines which mission planner module is used by ISAR
86
89
  MISSION_PLANNER: str = Field(default="local")
87
90
 
@@ -311,7 +314,7 @@ class RobotSettings(BaseSettings):
311
314
 
312
315
  # ISAR steps the robot is capable of performing
313
316
  # This should be set in the robot package settings.env file
314
- CAPABILITIES: List[str] = Field(default=["return_to_home", "take_image"])
317
+ CAPABILITIES: List[str] = Field(default=["take_image"])
315
318
 
316
319
  # Model of the robot which ISAR is connected to
317
320
  # This should be set in the robot package settings.env file
@@ -29,6 +29,7 @@ class APIRequests:
29
29
  self.stop_mission: QueueIO = QueueIO(input_size=1, output_size=1)
30
30
  self.pause_mission: QueueIO = QueueIO(input_size=1, output_size=1)
31
31
  self.resume_mission: QueueIO = QueueIO(input_size=1, output_size=1)
32
+ self.return_home: QueueIO = QueueIO(input_size=1, output_size=1)
32
33
 
33
34
 
34
35
  class StateMachineEvents:
@@ -30,3 +30,9 @@ def check_for_event(queue: Queue[T]) -> Optional[T]:
30
30
  return queue.get(block=False)
31
31
  except Empty:
32
32
  return None
33
+
34
+
35
+ def check_for_event_without_consumption(queue: Queue[T]) -> bool:
36
+ return (
37
+ queue.qsize() != 0
38
+ ) # Queue size is not reliable, but should be sufficient for this case
isar/modules.py CHANGED
@@ -70,12 +70,19 @@ class ApplicationContainer(containers.DeclarativeContainer):
70
70
  )
71
71
 
72
72
  # Storage
73
- local_storage = providers.Singleton(LocalStorage)
74
- blob_storage = providers.Singleton(BlobStorage, keyvault=keyvault)
75
- slimm_storage = providers.Singleton(
76
- SlimmStorage, request_handler=providers.Singleton(RequestHandler)
77
- )
78
- storage_handlers = providers.List(local_storage, blob_storage, slimm_storage)
73
+ storage_handlers_temp = []
74
+ if settings.STORAGE_LOCAL_ENABLED:
75
+ local_storage = providers.Singleton(LocalStorage)
76
+ storage_handlers_temp.append(local_storage)
77
+ if settings.STORAGE_BLOB_ENABLED:
78
+ blob_storage = providers.Singleton(BlobStorage, keyvault=keyvault)
79
+ storage_handlers_temp.append(blob_storage)
80
+ if settings.STORAGE_SLIMM_ENABLED:
81
+ slimm_storage = providers.Singleton(
82
+ SlimmStorage, request_handler=providers.Singleton(RequestHandler)
83
+ )
84
+ storage_handlers_temp.append(slimm_storage)
85
+ storage_handlers = providers.List(*storage_handlers_temp)
79
86
 
80
87
  # Mqtt client
81
88
  mqtt_client = (
isar/robot/robot.py CHANGED
@@ -11,11 +11,12 @@ from isar.models.communication.queues.events import (
11
11
  SharedState,
12
12
  StateMachineEvents,
13
13
  )
14
- from isar.models.communication.queues.queue_utils import check_for_event
14
+ from isar.models.communication.queues.queue_utils import check_for_event, trigger_event
15
15
  from isar.robot.robot_start_mission import RobotStartMissionThread
16
16
  from isar.robot.robot_status import RobotStatusThread
17
17
  from isar.robot.robot_stop_mission import RobotStopMissionThread
18
18
  from isar.robot.robot_task_status import RobotTaskStatusThread
19
+ from robot_interface.models.exceptions.robot_exceptions import ErrorMessage, ErrorReason
19
20
  from robot_interface.robot_interface import RobotInterface
20
21
 
21
22
 
@@ -94,6 +95,14 @@ class Robot(object):
94
95
  self.stop_mission_thread_thread is not None
95
96
  and self.start_mission_thread.is_alive()
96
97
  ):
98
+ error_description = "Received stop mission event while trying to start a mission. Aborting stop attempt."
99
+ error_message = ErrorMessage(
100
+ error_reason=ErrorReason.RobotStillStartingMissionException,
101
+ error_description=error_description,
102
+ )
103
+ trigger_event(
104
+ self.robot_service_events.mission_failed_to_stop, error_message
105
+ )
97
106
  return
98
107
  self.stop_mission_thread_thread = RobotStopMissionThread(
99
108
  self.robot_service_events, self.robot, self.signal_thread_quitting
@@ -5,6 +5,7 @@ from threading import Event, Thread
5
5
  from isar.config.settings import settings
6
6
  from isar.models.communication.queues.events import SharedState
7
7
  from isar.models.communication.queues.queue_utils import update_shared_state
8
+ from robot_interface.models.exceptions.robot_exceptions import RobotException
8
9
  from robot_interface.robot_interface import RobotInterface
9
10
 
10
11
 
@@ -19,7 +20,9 @@ class RobotStatusThread(Thread):
19
20
  self.shared_state: SharedState = shared_state
20
21
  self.robot: RobotInterface = robot
21
22
  self.signal_thread_quitting: Event = signal_thread_quitting
22
- self.last_robot_status_poll_time: float = time.time()
23
+ self.last_robot_status_poll_time: float = (
24
+ time.time() - settings.ROBOT_API_STATUS_POLL_INTERVAL
25
+ )
23
26
  Thread.__init__(self, name="Robot status thread")
24
27
 
25
28
  def stop(self) -> None:
@@ -40,9 +43,12 @@ class RobotStatusThread(Thread):
40
43
  while not self.signal_thread_quitting.wait(0.001):
41
44
  if not self._is_ready_to_poll_for_status():
42
45
  continue
46
+ try:
47
+ robot_status = self.robot.robot_status()
43
48
 
44
- robot_status = self.robot.robot_status()
45
-
46
- update_shared_state(self.shared_state.robot_status, robot_status)
47
- self.last_robot_status_poll_time = time.time()
49
+ update_shared_state(self.shared_state.robot_status, robot_status)
50
+ self.last_robot_status_poll_time = time.time()
51
+ except RobotException as e:
52
+ self.logger.error(f"Failed to retrieve robot status: {e}")
53
+ continue
48
54
  self.logger.info("Exiting robot status thread")
@@ -119,19 +119,47 @@ class SchedulingUtilities:
119
119
  return True
120
120
 
121
121
  def verify_state_machine_ready_to_receive_mission(self, state: States) -> bool:
122
- """Verify that the state machine is idle and ready to receive a mission
122
+ """Verify that the state machine is ready to receive a mission
123
123
 
124
124
  Raises
125
125
  ------
126
126
  HTTPException 409 Conflict
127
- If state machine is not idle and therefore can not start a new mission
127
+ If state machine is not home, robot standing still, awaiting next mission
128
+ or returning home and therefore cannot start a new mission
128
129
  """
129
- if state != States.Idle:
130
- error_message = f"Conflict - Robot is not idle - State: {state}"
131
- self.logger.warning(error_message)
132
- raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
130
+ if (
131
+ state == States.RobotStandingStill
132
+ or state == States.Home
133
+ or state == States.AwaitNextMission
134
+ or state == States.ReturningHome
135
+ ):
136
+ return True
133
137
 
134
- return True
138
+ error_message = f"Conflict - Robot is not home, robot standing still, awaiting next mission or returning home - State: {state}"
139
+ self.logger.warning(error_message)
140
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
141
+
142
+ def verify_state_machine_ready_to_receive_return_home_mission(
143
+ self, state: States
144
+ ) -> bool:
145
+ """Verify that the state machine is ready to receive a return home mission
146
+
147
+ Raises
148
+ ------
149
+ HTTPException 409 Conflict
150
+ If state machine is not home, robot standing still or awaiting next mission
151
+ and therefore cannot start a new return home mission
152
+ """
153
+ if (
154
+ state == States.RobotStandingStill
155
+ or state == States.Home
156
+ or state == States.AwaitNextMission
157
+ ):
158
+ return True
159
+
160
+ error_message = f"Conflict - Robot is not home, robot standing still or awaiting next mission - State: {state}"
161
+ self.logger.warning(error_message)
162
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
135
163
 
136
164
  def start_mission(
137
165
  self,
@@ -157,6 +185,31 @@ class SchedulingUtilities:
157
185
  )
158
186
  self.logger.info("OK - Mission started in ISAR")
159
187
 
188
+ def return_home(
189
+ self,
190
+ ) -> None:
191
+ """Start return home mission
192
+
193
+ Raises
194
+ ------
195
+ HTTTPException 408 Request timeout
196
+ If there is a timeout while communicating with the state machine
197
+ """
198
+ try:
199
+ self._send_command(
200
+ True,
201
+ self.api_events.return_home,
202
+ )
203
+ except QueueTimeoutError:
204
+ error_message = (
205
+ "Internal Server Error - Failed to start return home mission in ISAR"
206
+ )
207
+ self.logger.error(error_message)
208
+ raise HTTPException(
209
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
210
+ )
211
+ self.logger.info("OK - Return home mission started in ISAR")
212
+
160
213
  def pause_mission(self) -> ControlMissionResponse:
161
214
  """Pause mission
162
215
 
@@ -17,32 +17,20 @@ from isar.mission_planner.task_selector_interface import (
17
17
  )
18
18
  from isar.models.communication.queues.events import Events, SharedState
19
19
  from isar.models.communication.queues.queue_utils import update_shared_state
20
+ from isar.state_machine.states.await_next_mission import AwaitNextMission
20
21
  from isar.state_machine.states.blocked_protective_stop import BlockedProtectiveStop
21
- from isar.state_machine.states.idle import Idle
22
+ from isar.state_machine.states.home import Home
22
23
  from isar.state_machine.states.monitor import Monitor
23
- from isar.state_machine.states.off import Off
24
24
  from isar.state_machine.states.offline import Offline
25
25
  from isar.state_machine.states.paused import Paused
26
- from isar.state_machine.states.stop import Stop
26
+ from isar.state_machine.states.returning_home import ReturningHome
27
+ from isar.state_machine.states.robot_standing_still import RobotStandingStill
28
+ from isar.state_machine.states.stopping import Stopping
29
+ from isar.state_machine.states.unknown_status import UnknownStatus
27
30
  from isar.state_machine.states_enum import States
28
- from isar.state_machine.transitions.fail_mission import (
29
- report_failed_mission_and_finalize,
30
- )
31
- from isar.state_machine.transitions.finish_mission import finish_mission
32
- from isar.state_machine.transitions.pause import pause_mission
33
- from isar.state_machine.transitions.resume import resume_mission
34
- from isar.state_machine.transitions.start_mission import (
35
- initialize_robot,
36
- initiate_mission,
37
- put_start_mission_on_queue,
38
- set_mission_to_in_progress,
39
- trigger_start_mission_or_task_event,
40
- )
41
- from isar.state_machine.transitions.stop import (
42
- stop_mission_cleanup,
43
- trigger_stop_mission_event,
44
- )
45
- from isar.state_machine.transitions.utils import def_transition
31
+ from isar.state_machine.transitions.mission import get_mission_transitions
32
+ from isar.state_machine.transitions.return_home import get_return_home_transitions
33
+ from isar.state_machine.transitions.robot_status import get_robot_status_transitions
46
34
  from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
47
35
  from robot_interface.models.mission.mission import Mission
48
36
  from robot_interface.models.mission.status import RobotStatus, TaskStatus
@@ -101,114 +89,49 @@ class StateMachine(object):
101
89
  self.signal_state_machine_to_stop: Event = Event()
102
90
 
103
91
  # List of states
104
- self.stop_state: State = Stop(self)
105
- self.paused_state: State = Paused(self)
106
- self.idle_state: State = Idle(self)
92
+ # States running mission
107
93
  self.monitor_state: State = Monitor(self)
108
- self.off_state: State = Off(self)
94
+ self.returning_home_state: State = ReturningHome(self)
95
+ self.stopping_state: State = Stopping(self)
96
+ self.paused_state: State = Paused(self)
97
+
98
+ # States Waiting for mission
99
+ self.await_next_mission_state: State = AwaitNextMission(self)
100
+ self.home_state: State = Home(self)
101
+ self.robot_standing_still_state: State = RobotStandingStill(self)
102
+
103
+ # Status states
109
104
  self.offline_state: State = Offline(self)
110
- self.blocked_protective_stop: State = BlockedProtectiveStop(self)
105
+ self.blocked_protective_stopping_state: State = BlockedProtectiveStop(self)
106
+
107
+ # Error and special status states
108
+ self.unknown_status_state: State = UnknownStatus(self)
111
109
 
112
110
  self.states: List[State] = [
113
- self.off_state,
114
- self.idle_state,
115
111
  self.monitor_state,
116
- self.stop_state,
112
+ self.returning_home_state,
113
+ self.stopping_state,
117
114
  self.paused_state,
115
+ self.await_next_mission_state,
116
+ self.home_state,
117
+ self.robot_standing_still_state,
118
118
  self.offline_state,
119
- self.blocked_protective_stop,
119
+ self.blocked_protective_stopping_state,
120
+ self.unknown_status_state,
120
121
  ]
121
122
 
122
- self.machine = Machine(self, states=self.states, initial="off", queued=True)
123
- self.machine.add_transitions(
124
- [
125
- {
126
- "trigger": "start_machine",
127
- "source": self.off_state,
128
- "dest": self.idle_state,
129
- },
130
- {
131
- "trigger": "pause",
132
- "source": self.monitor_state,
133
- "dest": self.paused_state,
134
- "before": def_transition(self, pause_mission),
135
- },
136
- {
137
- "trigger": "stop",
138
- "source": [
139
- self.idle_state,
140
- self.monitor_state,
141
- self.paused_state,
142
- ],
143
- "dest": self.stop_state,
144
- "before": def_transition(self, trigger_stop_mission_event),
145
- },
146
- {
147
- "trigger": "request_mission_start",
148
- "source": self.idle_state,
149
- "dest": self.monitor_state,
150
- "prepare": def_transition(self, put_start_mission_on_queue),
151
- "conditions": [
152
- def_transition(self, initiate_mission),
153
- def_transition(self, initialize_robot),
154
- ],
155
- "before": [
156
- def_transition(self, set_mission_to_in_progress),
157
- def_transition(self, trigger_start_mission_or_task_event),
158
- ],
159
- },
160
- {
161
- "trigger": "request_mission_start",
162
- "source": self.idle_state,
163
- "dest": self.idle_state,
164
- },
165
- {
166
- "trigger": "mission_failed_to_start",
167
- "source": self.monitor_state,
168
- "dest": self.idle_state,
169
- "before": def_transition(self, report_failed_mission_and_finalize),
170
- },
171
- {
172
- "trigger": "resume",
173
- "source": self.paused_state,
174
- "dest": self.monitor_state,
175
- "before": def_transition(self, resume_mission),
176
- },
177
- {
178
- "trigger": "mission_finished",
179
- "source": self.monitor_state,
180
- "dest": self.idle_state,
181
- "before": def_transition(self, finish_mission),
182
- },
183
- {
184
- "trigger": "mission_stopped",
185
- "source": self.stop_state,
186
- "dest": self.idle_state,
187
- "before": def_transition(self, stop_mission_cleanup),
188
- },
189
- {
190
- "trigger": "robot_turned_offline",
191
- "source": [self.idle_state, self.blocked_protective_stop],
192
- "dest": self.offline_state,
193
- },
194
- {
195
- "trigger": "robot_turned_online",
196
- "source": self.offline_state,
197
- "dest": self.idle_state,
198
- },
199
- {
200
- "trigger": "robot_protective_stop_engaged",
201
- "source": [self.idle_state, self.offline_state],
202
- "dest": self.blocked_protective_stop,
203
- },
204
- {
205
- "trigger": "robot_protective_stop_disengaged",
206
- "source": self.blocked_protective_stop,
207
- "dest": self.idle_state,
208
- },
209
- ]
123
+ self.machine = Machine(
124
+ self, states=self.states, initial="unknown_status", queued=True
210
125
  )
211
126
 
127
+ self.transitions: List[dict] = []
128
+
129
+ self.transitions.extend(get_mission_transitions(self))
130
+ self.transitions.extend(get_return_home_transitions(self))
131
+ self.transitions.extend(get_robot_status_transitions(self))
132
+
133
+ self.machine.add_transitions(self.transitions)
134
+
212
135
  self.stop_robot_attempts_limit: int = stop_robot_attempts_limit
213
136
  self.sleep_time: float = sleep_time
214
137
 
@@ -237,8 +160,8 @@ class StateMachine(object):
237
160
  self.reset_state_machine()
238
161
 
239
162
  def begin(self):
240
- """Starts the state machine. Transitions into idle state."""
241
- self.to_idle() # type: ignore
163
+ """Starts the state machine. Transitions into unknown status state."""
164
+ self.robot_status_changed() # type: ignore
242
165
 
243
166
  def terminate(self):
244
167
  self.logger.info("Stopping state machine")
@@ -358,8 +281,15 @@ class StateMachine(object):
358
281
  )
359
282
 
360
283
  def _current_status(self) -> RobotStatus:
361
- if self.current_state == States.Idle:
284
+ if (
285
+ self.current_state == States.RobotStandingStill
286
+ or self.current_state == States.AwaitNextMission
287
+ ):
362
288
  return RobotStatus.Available
289
+ elif self.current_state == States.Home:
290
+ return RobotStatus.Home
291
+ elif self.current_state == States.ReturningHome:
292
+ return RobotStatus.ReturningHome
363
293
  elif self.current_state == States.Offline:
364
294
  return RobotStatus.Offline
365
295
  elif self.current_state == States.BlockedProtectiveStop:
@@ -0,0 +1,92 @@
1
+ import logging
2
+ import time
3
+ from queue import Queue
4
+ from typing import TYPE_CHECKING, Optional
5
+
6
+ from transitions import State
7
+
8
+ from isar.config.settings import settings
9
+ from isar.models.communication.message import StartMissionMessage
10
+ from isar.models.communication.queues.queue_io import QueueIO
11
+ from isar.models.communication.queues.queue_utils import check_for_event
12
+
13
+ if TYPE_CHECKING:
14
+ from isar.state_machine.state_machine import StateMachine
15
+
16
+
17
+ class AwaitNextMission(State):
18
+ def __init__(self, state_machine: "StateMachine") -> None:
19
+ super().__init__(
20
+ name="await_next_mission", on_enter=self.start, on_exit=self.stop
21
+ )
22
+ self.state_machine: "StateMachine" = state_machine
23
+ self.logger = logging.getLogger("state_machine")
24
+ self.entered_time: float = time.time()
25
+ self.return_home_delay: float = settings.RETURN_HOME_DELAY
26
+ self.events = self.state_machine.events
27
+ self.shared_state = self.state_machine.shared_state
28
+ self.signal_state_machine_to_stop = state_machine.signal_state_machine_to_stop
29
+
30
+ def start(self) -> None:
31
+ self.state_machine.update_state()
32
+ self.entered_time = time.time()
33
+ self._run()
34
+
35
+ def stop(self) -> None:
36
+ pass
37
+
38
+ def _check_and_handle_stop_mission_event(self, event: Queue) -> bool:
39
+ if check_for_event(event):
40
+ self.state_machine.stop() # type: ignore
41
+ return True
42
+ return False
43
+
44
+ def _check_and_handle_start_mission_event(
45
+ self, event: Queue[StartMissionMessage]
46
+ ) -> bool:
47
+ start_mission: Optional[StartMissionMessage] = check_for_event(event)
48
+ if start_mission:
49
+ self.state_machine.start_mission(mission=start_mission.mission)
50
+ self.state_machine.request_mission_start() # type: ignore
51
+ return True
52
+ return False
53
+
54
+ def _check_and_handle_return_home_event(self, event: QueueIO) -> bool:
55
+ if check_for_event(event.input):
56
+ event.output.put(True)
57
+ self.state_machine.request_return_home() # type: ignore
58
+ return True
59
+ return False
60
+
61
+ def _should_return_home(self) -> bool:
62
+ time_since_entered = time.time() - self.entered_time
63
+ return time_since_entered > self.return_home_delay
64
+
65
+ def _run(self) -> None:
66
+ while True:
67
+ if self.signal_state_machine_to_stop.is_set():
68
+ self.logger.info(
69
+ "Stopping state machine from %s state", self.__class__.__name__
70
+ )
71
+ break
72
+
73
+ if self._check_and_handle_stop_mission_event(
74
+ self.events.api_requests.stop_mission.input
75
+ ):
76
+ break
77
+
78
+ if self._check_and_handle_start_mission_event(
79
+ self.events.api_requests.start_mission.input
80
+ ):
81
+ break
82
+
83
+ if self._check_and_handle_return_home_event(
84
+ self.events.api_requests.return_home
85
+ ):
86
+ break
87
+
88
+ if self._should_return_home():
89
+ self.state_machine.request_return_home() # type: ignore
90
+ break
91
+
92
+ time.sleep(self.state_machine.sleep_time)
@@ -41,11 +41,8 @@ class BlockedProtectiveStop(State):
41
41
  robot_status: RobotStatus = check_shared_state(
42
42
  self.shared_state.robot_status
43
43
  )
44
- if robot_status == RobotStatus.Offline:
45
- transition = self.state_machine.robot_turned_offline # type: ignore
46
- break
47
- elif robot_status != RobotStatus.BlockedProtectiveStop:
48
- transition = self.state_machine.robot_protective_stop_disengaged # type: ignore
44
+ if robot_status != RobotStatus.BlockedProtectiveStop:
45
+ transition = self.state_machine.robot_status_changed # type: ignore
49
46
  break
50
47
 
51
48
  time.sleep(self.state_machine.sleep_time)