isar 1.31.0__py3-none-any.whl → 1.32.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 (58) hide show
  1. isar/apis/api.py +18 -0
  2. isar/apis/schedule/scheduling_controller.py +16 -0
  3. isar/config/log.py +1 -2
  4. isar/config/logging.conf +9 -30
  5. isar/config/open_telemetry.py +2 -8
  6. isar/config/settings.py +4 -0
  7. isar/eventhandlers/eventhandler.py +93 -0
  8. isar/models/events.py +119 -0
  9. isar/modules.py +1 -1
  10. isar/robot/robot.py +16 -18
  11. isar/robot/robot_start_mission.py +8 -14
  12. isar/robot/robot_status.py +2 -3
  13. isar/robot/robot_stop_mission.py +3 -9
  14. isar/robot/robot_task_status.py +3 -7
  15. isar/script.py +9 -17
  16. isar/services/utilities/scheduling_utilities.py +45 -23
  17. isar/state_machine/state_machine.py +104 -9
  18. isar/state_machine/states/await_next_mission.py +46 -11
  19. isar/state_machine/states/blocked_protective_stop.py +24 -15
  20. isar/state_machine/states/home.py +40 -9
  21. isar/state_machine/states/intervention_needed.py +43 -0
  22. isar/state_machine/states/monitor.py +83 -12
  23. isar/state_machine/states/offline.py +25 -13
  24. isar/state_machine/states/paused.py +24 -38
  25. isar/state_machine/states/returning_home.py +75 -14
  26. isar/state_machine/states/robot_standing_still.py +41 -11
  27. isar/state_machine/states/stopping.py +52 -67
  28. isar/state_machine/states/unknown_status.py +37 -64
  29. isar/state_machine/states_enum.py +1 -0
  30. isar/state_machine/transitions/functions/fail_mission.py +7 -0
  31. isar/state_machine/transitions/functions/robot_status.py +4 -5
  32. isar/state_machine/transitions/functions/stop.py +3 -12
  33. isar/state_machine/transitions/mission.py +0 -6
  34. isar/state_machine/transitions/return_home.py +14 -2
  35. isar/state_machine/utils/common_event_handlers.py +166 -0
  36. isar/storage/uploader.py +1 -1
  37. {isar-1.31.0.dist-info → isar-1.32.0.dist-info}/METADATA +1 -1
  38. {isar-1.31.0.dist-info → isar-1.32.0.dist-info}/RECORD +44 -54
  39. robot_interface/models/mission/status.py +1 -0
  40. robot_interface/telemetry/payloads.py +8 -0
  41. isar/models/communication/__init__.py +0 -0
  42. isar/models/communication/message.py +0 -8
  43. isar/models/communication/queues/__init__.py +0 -0
  44. isar/models/communication/queues/events.py +0 -58
  45. isar/models/communication/queues/queue_io.py +0 -12
  46. isar/models/communication/queues/queue_timeout_error.py +0 -2
  47. isar/models/communication/queues/queue_utils.py +0 -38
  48. isar/models/communication/queues/status_queue.py +0 -22
  49. isar/models/mission_metadata/__init__.py +0 -0
  50. isar/services/service_connections/stid/__init__.py +0 -0
  51. isar/services/utilities/queue_utilities.py +0 -39
  52. isar/state_machine/generic_states/idle.py +0 -133
  53. isar/state_machine/generic_states/ongoing_mission.py +0 -309
  54. isar/state_machine/generic_states/robot_unavailable.py +0 -61
  55. {isar-1.31.0.dist-info → isar-1.32.0.dist-info}/WHEEL +0 -0
  56. {isar-1.31.0.dist-info → isar-1.32.0.dist-info}/entry_points.txt +0 -0
  57. {isar-1.31.0.dist-info → isar-1.32.0.dist-info}/licenses/LICENSE +0 -0
  58. {isar-1.31.0.dist-info → isar-1.32.0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,7 @@
1
1
  import logging
2
2
  from copy import deepcopy
3
3
  from http import HTTPStatus
4
- from queue import Empty
5
- from typing import Any, List
4
+ from typing import List, TypeVar
6
5
 
7
6
  from fastapi import HTTPException
8
7
  from requests import HTTPError
@@ -14,15 +13,20 @@ from isar.mission_planner.mission_planner_interface import (
14
13
  MissionPlannerError,
15
14
  MissionPlannerInterface,
16
15
  )
17
- from isar.models.communication.message import StartMissionMessage
18
- from isar.models.communication.queues.events import APIRequests, Events, SharedState
19
- from isar.models.communication.queues.queue_io import QueueIO
20
- from isar.models.communication.queues.queue_timeout_error import QueueTimeoutError
21
- from isar.services.utilities.queue_utilities import QueueUtilities
16
+ from isar.models.events import (
17
+ APIEvent,
18
+ APIRequests,
19
+ Events,
20
+ EventTimeoutError,
21
+ SharedState,
22
+ )
22
23
  from isar.state_machine.states_enum import States
23
24
  from robot_interface.models.mission.mission import Mission
24
25
  from robot_interface.models.mission.status import MissionStatus
25
26
 
27
+ T1 = TypeVar("T1")
28
+ T2 = TypeVar("T2")
29
+
26
30
 
27
31
  class SchedulingUtilities:
28
32
  """
@@ -51,9 +55,8 @@ class SchedulingUtilities:
51
55
  HTTPException 500 Internal Server Error
52
56
  If the current state is not available on the queue
53
57
  """
54
- try:
55
- return self.shared_state.state.check()
56
- except Empty:
58
+ current_state = self.shared_state.state.check()
59
+ if current_state is None:
57
60
  error_message: str = (
58
61
  "Internal Server Error - Current state of the state machine is unknown"
59
62
  )
@@ -61,6 +64,7 @@ class SchedulingUtilities:
61
64
  raise HTTPException(
62
65
  status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
63
66
  )
67
+ return current_state
64
68
 
65
69
  def get_mission(self, mission_id: str) -> Mission:
66
70
  """Get the mission with mission_id from the current mission planner
@@ -173,10 +177,10 @@ class SchedulingUtilities:
173
177
  """
174
178
  try:
175
179
  self._send_command(
176
- StartMissionMessage(mission=deepcopy(mission)),
180
+ deepcopy(mission),
177
181
  self.api_events.start_mission,
178
182
  )
179
- except QueueTimeoutError:
183
+ except EventTimeoutError:
180
184
  error_message = "Internal Server Error - Failed to start mission in ISAR"
181
185
  self.logger.error(error_message)
182
186
  raise HTTPException(
@@ -199,7 +203,7 @@ class SchedulingUtilities:
199
203
  True,
200
204
  self.api_events.return_home,
201
205
  )
202
- except QueueTimeoutError:
206
+ except EventTimeoutError:
203
207
  error_message = (
204
208
  "Internal Server Error - Failed to start return home mission in ISAR"
205
209
  )
@@ -221,7 +225,7 @@ class SchedulingUtilities:
221
225
  response = self._send_command(True, self.api_events.pause_mission)
222
226
  self.logger.info("OK - Mission successfully paused")
223
227
  return response
224
- except QueueTimeoutError:
228
+ except EventTimeoutError:
225
229
  error_message = "Internal Server Error - Failed to pause mission"
226
230
  self.logger.error(error_message)
227
231
  raise HTTPException(
@@ -240,7 +244,7 @@ class SchedulingUtilities:
240
244
  response = self._send_command(True, self.api_events.resume_mission)
241
245
  self.logger.info("OK - Mission successfully resumed")
242
246
  return response
243
- except QueueTimeoutError:
247
+ except EventTimeoutError:
244
248
  error_message = "Internal Server Error - Failed to resume mission"
245
249
  self.logger.error(error_message)
246
250
  raise HTTPException(
@@ -277,7 +281,7 @@ class SchedulingUtilities:
277
281
  raise HTTPException(
278
282
  status_code=HTTPStatus.CONFLICT, detail=error_message
279
283
  )
280
- except QueueTimeoutError:
284
+ except EventTimeoutError:
281
285
  error_message = "Internal Server Error - Failed to stop mission"
282
286
  self.logger.error(error_message)
283
287
  raise HTTPException(
@@ -286,14 +290,32 @@ class SchedulingUtilities:
286
290
  self.logger.info("OK - Mission successfully stopped")
287
291
  return stop_mission_response
288
292
 
289
- def _send_command(self, input: Any, queueio: QueueIO) -> Any:
290
- queueio.input.put(input)
293
+ def release_intervention_needed(self) -> None:
294
+ """Release intervention needed state
295
+
296
+ Raises
297
+ ------
298
+ HTTPException 500 Internal Server Error
299
+ If the intervention needed state could not be released
300
+ """
291
301
  try:
292
- return QueueUtilities.check_queue(
293
- queueio.output,
294
- self.queue_timeout,
302
+ self._send_command(True, self.api_events.release_intervention_needed)
303
+ self.logger.info("OK - Intervention needed state released")
304
+ except EventTimeoutError:
305
+ error_message = (
306
+ "Internal Server Error - Failed to release intervention needed state"
295
307
  )
296
- except QueueTimeoutError as e:
297
- QueueUtilities.clear_queue(queueio.input)
308
+ self.logger.error(error_message)
309
+ raise HTTPException(
310
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
311
+ )
312
+
313
+ def _send_command(self, input: T1, api_event: APIEvent[T1, T2]) -> T2:
314
+ api_event.input.trigger_event(input)
315
+ try:
316
+ return api_event.output.consume_event(timeout=self.queue_timeout)
317
+ except EventTimeoutError as e:
318
+ self.logger.error("Queue timed out")
319
+ api_event.input.clear_event()
298
320
  self.logger.error("No output received for command to state machine")
299
321
  raise e
@@ -3,7 +3,7 @@ import logging
3
3
  from collections import deque
4
4
  from datetime import datetime, timezone
5
5
  from threading import Event
6
- from typing import Deque, List, Optional
6
+ from typing import Deque, List, Optional, Tuple
7
7
 
8
8
  from transitions import Machine
9
9
  from transitions.core import State
@@ -14,11 +14,11 @@ from isar.mission_planner.task_selector_interface import (
14
14
  TaskSelectorInterface,
15
15
  TaskSelectorStop,
16
16
  )
17
- from isar.models.communication.queues.events import Events, SharedState
18
- from isar.models.communication.queues.queue_utils import update_shared_state
17
+ from isar.models.events import Events, SharedState
19
18
  from isar.state_machine.states.await_next_mission import AwaitNextMission
20
19
  from isar.state_machine.states.blocked_protective_stop import BlockedProtectiveStop
21
20
  from isar.state_machine.states.home import Home
21
+ from isar.state_machine.states.intervention_needed import InterventionNeeded
22
22
  from isar.state_machine.states.monitor import Monitor
23
23
  from isar.state_machine.states.offline import Offline
24
24
  from isar.state_machine.states.paused import Paused
@@ -30,13 +30,19 @@ from isar.state_machine.states_enum import States
30
30
  from isar.state_machine.transitions.mission import get_mission_transitions
31
31
  from isar.state_machine.transitions.return_home import get_return_home_transitions
32
32
  from isar.state_machine.transitions.robot_status import get_robot_status_transitions
33
- from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
33
+ from robot_interface.models.exceptions.robot_exceptions import (
34
+ ErrorMessage,
35
+ RobotException,
36
+ RobotRetrieveInspectionException,
37
+ )
38
+ from robot_interface.models.inspection.inspection import Inspection
34
39
  from robot_interface.models.mission.mission import Mission
35
40
  from robot_interface.models.mission.status import RobotStatus, TaskStatus
36
- from robot_interface.models.mission.task import TASKS
41
+ from robot_interface.models.mission.task import TASKS, InspectionTask, Task
37
42
  from robot_interface.robot_interface import RobotInterface
38
43
  from robot_interface.telemetry.mqtt_client import MqttClientInterface
39
44
  from robot_interface.telemetry.payloads import (
45
+ InterventionNeededPayload,
40
46
  MissionPayload,
41
47
  RobotStatusPayload,
42
48
  TaskPayload,
@@ -97,6 +103,7 @@ class StateMachine(object):
97
103
  self.await_next_mission_state: State = AwaitNextMission(self)
98
104
  self.home_state: State = Home(self)
99
105
  self.robot_standing_still_state: State = RobotStandingStill(self)
106
+ self.intervention_needed_state: State = InterventionNeeded(self)
100
107
 
101
108
  # Status states
102
109
  self.offline_state: State = Offline(self)
@@ -116,6 +123,7 @@ class StateMachine(object):
116
123
  self.offline_state,
117
124
  self.blocked_protective_stopping_state,
118
125
  self.unknown_status_state,
126
+ self.intervention_needed_state,
119
127
  ]
120
128
 
121
129
  self.machine = Machine(
@@ -140,6 +148,8 @@ class StateMachine(object):
140
148
 
141
149
  self.current_state: State = States(self.state) # type: ignore
142
150
 
151
+ self.awaiting_task_status: bool = False
152
+
143
153
  self.transitions_log_length: int = transitions_log_length
144
154
  self.transitions_list: Deque[States] = deque([], self.transitions_log_length)
145
155
 
@@ -182,7 +192,7 @@ class StateMachine(object):
182
192
  def update_state(self):
183
193
  """Updates the current state of the state machine."""
184
194
  self.current_state = States(self.state) # type: ignore
185
- update_shared_state(self.shared_state.state, self.current_state)
195
+ self.shared_state.state.update(self.current_state)
186
196
  self._log_state_transition(self.current_state)
187
197
  self.logger.info("State: %s", self.current_state)
188
198
  self.publish_status()
@@ -201,9 +211,21 @@ class StateMachine(object):
201
211
  self.task_selector.initialize(tasks=self.current_mission.tasks)
202
212
 
203
213
  def send_task_status(self):
204
- update_shared_state(
205
- self.shared_state.state_machine_current_task, self.current_task
206
- )
214
+ self.shared_state.state_machine_current_task.update(self.current_task)
215
+
216
+ def report_task_status(self, task: Task) -> None:
217
+ if task.status == TaskStatus.Failed:
218
+ self.logger.warning(
219
+ f"Task: {str(task.id)[:8]} was reported as failed by the robot"
220
+ )
221
+ elif task.status == TaskStatus.Successful:
222
+ self.logger.info(
223
+ f"{type(task).__name__} task: {str(task.id)[:8]} completed"
224
+ )
225
+ else:
226
+ self.logger.info(
227
+ f"Task: {str(task.id)[:8]} was reported as task.status by the robot"
228
+ )
207
229
 
208
230
  def publish_mission_status(self) -> None:
209
231
  if not self.mqtt_publisher:
@@ -264,6 +286,25 @@ class StateMachine(object):
264
286
  retain=True,
265
287
  )
266
288
 
289
+ def publish_intervention_needed(self, error_message: str) -> None:
290
+ """Publishes the intervention needed message to the MQTT Broker"""
291
+ if not self.mqtt_publisher:
292
+ return
293
+
294
+ payload: InterventionNeededPayload = InterventionNeededPayload(
295
+ isar_id=settings.ISAR_ID,
296
+ robot_name=settings.ROBOT_NAME,
297
+ reason=error_message,
298
+ timestamp=datetime.now(timezone.utc),
299
+ )
300
+
301
+ self.mqtt_publisher.publish(
302
+ topic=settings.TOPIC_ISAR_INTERVENTION_NEEDED,
303
+ payload=json.dumps(payload, cls=EnhancedJSONEncoder),
304
+ qos=1,
305
+ retain=True,
306
+ )
307
+
267
308
  def publish_status(self) -> None:
268
309
  if not self.mqtt_publisher:
269
310
  return
@@ -296,6 +337,8 @@ class StateMachine(object):
296
337
  return RobotStatus.Offline
297
338
  elif self.current_state == States.BlockedProtectiveStop:
298
339
  return RobotStatus.BlockedProtectiveStop
340
+ elif self.current_state == States.InterventionNeeded:
341
+ return RobotStatus.InterventionNeeded
299
342
  else:
300
343
  return RobotStatus.Busy
301
344
 
@@ -334,6 +377,58 @@ class StateMachine(object):
334
377
  )
335
378
  )
336
379
 
380
+ def should_upload_inspections(self) -> bool:
381
+ if settings.UPLOAD_INSPECTIONS_ASYNC:
382
+ return False
383
+
384
+ return (
385
+ self.current_task.is_finished()
386
+ and self.current_task.status == TaskStatus.Successful
387
+ and isinstance(self.current_task, InspectionTask)
388
+ )
389
+
390
+ def queue_inspections_for_upload(
391
+ self, mission: Mission, current_task: InspectionTask, logger: logging.Logger
392
+ ) -> None:
393
+ try:
394
+ inspection: Inspection = self.robot.get_inspection(task=current_task)
395
+ if current_task.inspection_id != inspection.id:
396
+ logger.warning(
397
+ f"The inspection_id of task ({current_task.inspection_id}) "
398
+ f"and result ({inspection.id}) is not matching. "
399
+ f"This may lead to confusions when accessing the inspection later"
400
+ )
401
+
402
+ except (RobotRetrieveInspectionException, RobotException) as e:
403
+ error_message: ErrorMessage = ErrorMessage(
404
+ error_reason=e.error_reason, error_description=e.error_description
405
+ )
406
+ self.current_task.error_message = error_message
407
+ logger.error(
408
+ f"Failed to retrieve inspections because: {e.error_description}"
409
+ )
410
+ return
411
+
412
+ except Exception as e:
413
+ logger.error(
414
+ f"Failed to retrieve inspections because of unexpected error: {e}"
415
+ )
416
+ return
417
+
418
+ if not inspection:
419
+ logger.warning(
420
+ f"No inspection result data retrieved for task {str(current_task.id)[:8]}"
421
+ )
422
+
423
+ inspection.metadata.tag_id = current_task.tag_id
424
+
425
+ message: Tuple[Inspection, Mission] = (
426
+ inspection,
427
+ mission,
428
+ )
429
+ self.events.upload_queue.put(message)
430
+ logger.info(f"Inspection result: {str(inspection.id)[:8]} queued for upload")
431
+
337
432
 
338
433
  def main(state_machine: StateMachine):
339
434
  """Starts a state machine instance."""
@@ -1,20 +1,55 @@
1
- from typing import TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, List
2
2
 
3
- from transitions import State
3
+ from isar.config.settings import settings
4
+ from isar.eventhandlers.eventhandler import (
5
+ EventHandlerBase,
6
+ EventHandlerMapping,
7
+ TimeoutHandlerMapping,
8
+ )
9
+ from isar.state_machine.utils.common_event_handlers import (
10
+ return_home_event_handler,
11
+ start_mission_event_handler,
12
+ stop_mission_event_handler,
13
+ )
4
14
 
5
15
  if TYPE_CHECKING:
6
16
  from isar.state_machine.state_machine import StateMachine
7
17
 
8
- from isar.state_machine.generic_states.idle import Idle, IdleStates
9
18
 
19
+ class AwaitNextMission(EventHandlerBase):
10
20
 
11
- class AwaitNextMission(State, Idle):
12
- def __init__(self, state_machine: "StateMachine") -> None:
13
- State.__init__(
14
- self, name="await_next_mission", on_enter=self.start, on_exit=self.stop
15
- )
16
- Idle.__init__(
17
- self,
21
+ def __init__(self, state_machine: "StateMachine"):
22
+ events = state_machine.events
23
+
24
+ event_handlers: List[EventHandlerMapping] = [
25
+ EventHandlerMapping(
26
+ name="start_mission_event",
27
+ event=events.api_requests.start_mission.input,
28
+ handler=lambda event: start_mission_event_handler(state_machine, event),
29
+ ),
30
+ EventHandlerMapping(
31
+ name="return_home_event",
32
+ event=events.api_requests.return_home.input,
33
+ handler=lambda event: return_home_event_handler(state_machine, event),
34
+ ),
35
+ EventHandlerMapping(
36
+ name="stop_mission_event",
37
+ event=events.api_requests.return_home.input,
38
+ handler=lambda event: stop_mission_event_handler(state_machine, event),
39
+ ),
40
+ ]
41
+
42
+ timers: List[TimeoutHandlerMapping] = [
43
+ TimeoutHandlerMapping(
44
+ name="should_return_home_timer",
45
+ timeout_in_seconds=settings.RETURN_HOME_DELAY,
46
+ handler=lambda: state_machine.request_return_home, # type: ignore
47
+ )
48
+ ]
49
+
50
+ super().__init__(
51
+ state_name="await_next_mission",
18
52
  state_machine=state_machine,
19
- state=IdleStates.AwaitNextMission,
53
+ event_handler_mappings=event_handlers,
54
+ timers=timers,
20
55
  )
@@ -1,24 +1,33 @@
1
- from typing import TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, List
2
2
 
3
- from transitions import State
4
-
5
- from isar.state_machine.generic_states.robot_unavailable import (
6
- RobotUnavailable,
7
- RobotUnavailableStates,
8
- )
3
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
4
+ from isar.models.events import Event
5
+ from robot_interface.models.mission.status import RobotStatus
9
6
 
10
7
  if TYPE_CHECKING:
11
8
  from isar.state_machine.state_machine import StateMachine
12
9
 
13
10
 
14
- class BlockedProtectiveStop(State, RobotUnavailable):
15
- def __init__(self, state_machine: "StateMachine") -> None:
16
- State.__init__(
17
- self, name="blocked_protective_stop", on_enter=self.start, on_exit=self.stop
18
- )
11
+ class BlockedProtectiveStop(EventHandlerBase):
12
+
13
+ def __init__(self, state_machine: "StateMachine"):
14
+ shared_state = state_machine.shared_state
15
+
16
+ def _robot_status_event_handler(event: Event[RobotStatus]):
17
+ robot_status: RobotStatus = event.check()
18
+ if robot_status != RobotStatus.BlockedProtectiveStop:
19
+ return state_machine.robot_status_changed # type: ignore
20
+ return None
19
21
 
20
- RobotUnavailable.__init__(
21
- self,
22
+ event_handlers: List[EventHandlerMapping] = [
23
+ EventHandlerMapping(
24
+ name="robot_status_event",
25
+ event=shared_state.robot_status,
26
+ handler=_robot_status_event_handler,
27
+ ),
28
+ ]
29
+ super().__init__(
30
+ state_name="blocked_protective_stop",
22
31
  state_machine=state_machine,
23
- state=RobotUnavailableStates.BlockedProtectiveStop,
32
+ event_handler_mappings=event_handlers,
24
33
  )
@@ -1,19 +1,50 @@
1
- from typing import TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, List
2
2
 
3
- from transitions import State
3
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
4
+ from isar.state_machine.utils.common_event_handlers import (
5
+ return_home_event_handler,
6
+ robot_status_event_handler,
7
+ start_mission_event_handler,
8
+ stop_mission_event_handler,
9
+ )
10
+ from robot_interface.models.mission.status import RobotStatus
4
11
 
5
12
  if TYPE_CHECKING:
6
13
  from isar.state_machine.state_machine import StateMachine
7
14
 
8
- from isar.state_machine.generic_states.idle import Idle, IdleStates
9
15
 
16
+ class Home(EventHandlerBase):
10
17
 
11
- class Home(State, Idle):
12
- def __init__(self, state_machine: "StateMachine") -> None:
13
- State.__init__(self, name="home", on_enter=self.start, on_exit=self.stop)
18
+ def __init__(self, state_machine: "StateMachine"):
19
+ events = state_machine.events
20
+ shared_state = state_machine.shared_state
14
21
 
15
- Idle.__init__(
16
- self,
22
+ event_handlers: List[EventHandlerMapping] = [
23
+ EventHandlerMapping(
24
+ name="start_mission_event",
25
+ event=events.api_requests.start_mission.input,
26
+ handler=lambda event: start_mission_event_handler(state_machine, event),
27
+ ),
28
+ EventHandlerMapping(
29
+ name="return_home_event",
30
+ event=events.api_requests.return_home.input,
31
+ handler=lambda event: return_home_event_handler(state_machine, event),
32
+ ),
33
+ EventHandlerMapping(
34
+ name="stop_mission_event",
35
+ event=events.api_requests.return_home.input,
36
+ handler=lambda event: stop_mission_event_handler(state_machine, event),
37
+ ),
38
+ EventHandlerMapping(
39
+ name="robot_status_event",
40
+ event=shared_state.robot_status,
41
+ handler=lambda event: robot_status_event_handler(
42
+ state_machine, RobotStatus.Home, event
43
+ ),
44
+ ),
45
+ ]
46
+ super().__init__(
47
+ state_name="home",
17
48
  state_machine=state_machine,
18
- state=IdleStates.Home,
49
+ event_handler_mappings=event_handlers,
19
50
  )
@@ -0,0 +1,43 @@
1
+ from collections.abc import Callable
2
+ from typing import TYPE_CHECKING, List, Optional
3
+
4
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
5
+ from isar.models.events import Event
6
+ from isar.state_machine.utils.common_event_handlers import return_home_event_handler
7
+
8
+ if TYPE_CHECKING:
9
+ from isar.state_machine.state_machine import StateMachine
10
+
11
+
12
+ class InterventionNeeded(EventHandlerBase):
13
+
14
+ def __init__(self, state_machine: "StateMachine"):
15
+ events = state_machine.events
16
+
17
+ def release_intervention_needed_handler(
18
+ event: Event[bool],
19
+ ) -> Optional[Callable]:
20
+ if event.consume_event():
21
+ state_machine.events.api_requests.release_intervention_needed.output.trigger_event(
22
+ True
23
+ )
24
+ return state_machine.release_intervention_needed # type: ignore
25
+ return None
26
+
27
+ event_handlers: List[EventHandlerMapping] = [
28
+ EventHandlerMapping(
29
+ name="return_home_event",
30
+ event=events.api_requests.return_home.input,
31
+ handler=lambda event: return_home_event_handler(state_machine, event),
32
+ ),
33
+ EventHandlerMapping(
34
+ name="release_intervention_needed_event",
35
+ event=events.api_requests.release_intervention_needed.input,
36
+ handler=release_intervention_needed_handler,
37
+ ),
38
+ ]
39
+ super().__init__(
40
+ state_name="intervention_needed",
41
+ state_machine=state_machine,
42
+ event_handler_mappings=event_handlers,
43
+ )
@@ -1,22 +1,93 @@
1
- from typing import TYPE_CHECKING
1
+ import logging
2
+ from copy import deepcopy
3
+ from typing import TYPE_CHECKING, Callable, List, Optional
2
4
 
3
- from transitions import State
4
-
5
- from isar.state_machine.generic_states.ongoing_mission import (
6
- OngoingMission,
7
- OngoingMissionStates,
5
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
6
+ from isar.models.events import Event
7
+ from isar.services.utilities.threaded_request import ThreadedRequest
8
+ from isar.state_machine.utils.common_event_handlers import (
9
+ mission_failed_event_handler,
10
+ mission_started_event_handler,
11
+ stop_mission_event_handler,
12
+ task_status_event_handler,
13
+ task_status_failed_event_handler,
8
14
  )
15
+ from robot_interface.models.mission.status import TaskStatus
9
16
 
10
17
  if TYPE_CHECKING:
11
18
  from isar.state_machine.state_machine import StateMachine
12
19
 
13
20
 
14
- class Monitor(State, OngoingMission):
15
- def __init__(self, state_machine: "StateMachine") -> None:
16
- State.__init__(self, name="monitor", on_enter=self.start, on_exit=self.stop)
21
+ class Monitor(EventHandlerBase):
22
+
23
+ def __init__(self, state_machine: "StateMachine"):
24
+ logger = logging.getLogger("state_machine")
25
+ events = state_machine.events
26
+
27
+ def _pause_mission_event_handler(event: Event[bool]) -> Optional[Callable]:
28
+ if event.consume_event():
29
+ return state_machine.pause # type: ignore
30
+ return None
31
+
32
+ def _handle_task_completed(task_status: TaskStatus):
33
+ if state_machine.should_upload_inspections():
34
+ get_inspection_thread = ThreadedRequest(
35
+ state_machine.queue_inspections_for_upload
36
+ )
37
+ get_inspection_thread.start_thread(
38
+ deepcopy(state_machine.current_mission),
39
+ deepcopy(state_machine.current_task),
40
+ logger,
41
+ name="State Machine Get Inspections",
42
+ )
43
+
44
+ state_machine.iterate_current_task()
45
+ if state_machine.current_task is None:
46
+ return state_machine.mission_finished # type: ignore
47
+ return None
17
48
 
18
- OngoingMission.__init__(
19
- self,
49
+ event_handlers: List[EventHandlerMapping] = [
50
+ EventHandlerMapping(
51
+ name="stop_mission_event",
52
+ event=events.api_requests.stop_mission.input,
53
+ handler=lambda event: stop_mission_event_handler(state_machine, event),
54
+ ),
55
+ EventHandlerMapping(
56
+ name="pause_mission_event",
57
+ event=events.api_requests.pause_mission.input,
58
+ handler=_pause_mission_event_handler,
59
+ ),
60
+ EventHandlerMapping(
61
+ name="mission_started_event",
62
+ event=events.robot_service_events.mission_started,
63
+ handler=lambda event: mission_started_event_handler(
64
+ state_machine, event
65
+ ),
66
+ ),
67
+ EventHandlerMapping(
68
+ name="mission_failed_event",
69
+ event=events.robot_service_events.mission_failed,
70
+ handler=lambda event: mission_failed_event_handler(
71
+ state_machine, event
72
+ ),
73
+ ),
74
+ EventHandlerMapping(
75
+ name="task_status_failed_event",
76
+ event=events.robot_service_events.task_status_failed,
77
+ handler=lambda event: task_status_failed_event_handler(
78
+ state_machine, _handle_task_completed, event
79
+ ),
80
+ ),
81
+ EventHandlerMapping(
82
+ name="task_status_event",
83
+ event=events.robot_service_events.task_status_updated,
84
+ handler=lambda event: task_status_event_handler(
85
+ state_machine, _handle_task_completed, event
86
+ ),
87
+ ),
88
+ ]
89
+ super().__init__(
90
+ state_name="monitor",
20
91
  state_machine=state_machine,
21
- state=OngoingMissionStates.Monitor,
92
+ event_handler_mappings=event_handlers,
22
93
  )