isar 1.15.0__py3-none-any.whl → 1.34.9__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 (129) hide show
  1. isar/__init__.py +2 -5
  2. isar/apis/api.py +159 -66
  3. isar/apis/models/__init__.py +0 -1
  4. isar/apis/models/models.py +22 -12
  5. isar/apis/models/start_mission_definition.py +128 -123
  6. isar/apis/robot_control/robot_controller.py +41 -0
  7. isar/apis/schedule/scheduling_controller.py +135 -121
  8. isar/apis/security/authentication.py +5 -5
  9. isar/config/certs/ca-cert.pem +32 -32
  10. isar/config/keyvault/keyvault_service.py +1 -2
  11. isar/config/log.py +47 -39
  12. isar/config/logging.conf +16 -31
  13. isar/config/open_telemetry.py +102 -0
  14. isar/config/predefined_mission_definition/default_exr.json +49 -0
  15. isar/config/predefined_mission_definition/default_mission.json +1 -5
  16. isar/config/predefined_mission_definition/default_turtlebot.json +4 -11
  17. isar/config/predefined_missions/default.json +67 -87
  18. isar/config/predefined_missions/default_extra_capabilities.json +107 -0
  19. isar/config/settings.py +119 -142
  20. isar/eventhandlers/eventhandler.py +123 -0
  21. isar/mission_planner/local_planner.py +6 -20
  22. isar/mission_planner/mission_planner_interface.py +1 -1
  23. isar/models/events.py +184 -0
  24. isar/models/status.py +18 -0
  25. isar/modules.py +118 -205
  26. isar/robot/robot.py +377 -0
  27. isar/robot/robot_battery.py +60 -0
  28. isar/robot/robot_monitor_mission.py +357 -0
  29. isar/robot/robot_pause_mission.py +74 -0
  30. isar/robot/robot_resume_mission.py +67 -0
  31. isar/robot/robot_start_mission.py +66 -0
  32. isar/robot/robot_status.py +61 -0
  33. isar/robot/robot_stop_mission.py +68 -0
  34. isar/robot/robot_upload_inspection.py +75 -0
  35. isar/script.py +171 -0
  36. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  37. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +32 -0
  38. isar/services/service_connections/mqtt/robot_info_publisher.py +4 -3
  39. isar/services/service_connections/persistent_memory.py +69 -0
  40. isar/services/utilities/mqtt_utilities.py +93 -0
  41. isar/services/utilities/robot_utilities.py +20 -0
  42. isar/services/utilities/scheduling_utilities.py +393 -65
  43. isar/state_machine/state_machine.py +227 -486
  44. isar/state_machine/states/__init__.py +0 -7
  45. isar/state_machine/states/await_next_mission.py +114 -0
  46. isar/state_machine/states/blocked_protective_stop.py +60 -0
  47. isar/state_machine/states/going_to_lockdown.py +95 -0
  48. isar/state_machine/states/going_to_recharging.py +92 -0
  49. isar/state_machine/states/home.py +115 -0
  50. isar/state_machine/states/intervention_needed.py +77 -0
  51. isar/state_machine/states/lockdown.py +38 -0
  52. isar/state_machine/states/maintenance.py +36 -0
  53. isar/state_machine/states/monitor.py +137 -166
  54. isar/state_machine/states/offline.py +60 -0
  55. isar/state_machine/states/paused.py +92 -23
  56. isar/state_machine/states/pausing.py +48 -0
  57. isar/state_machine/states/pausing_return_home.py +48 -0
  58. isar/state_machine/states/recharging.py +80 -0
  59. isar/state_machine/states/resuming.py +57 -0
  60. isar/state_machine/states/resuming_return_home.py +64 -0
  61. isar/state_machine/states/return_home_paused.py +109 -0
  62. isar/state_machine/states/returning_home.py +217 -0
  63. isar/state_machine/states/stopping.py +61 -0
  64. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  65. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  66. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  67. isar/state_machine/states/stopping_return_home.py +77 -0
  68. isar/state_machine/states/unknown_status.py +72 -0
  69. isar/state_machine/states_enum.py +22 -5
  70. isar/state_machine/transitions/mission.py +192 -0
  71. isar/state_machine/transitions/return_home.py +106 -0
  72. isar/state_machine/transitions/robot_status.py +80 -0
  73. isar/state_machine/utils/common_event_handlers.py +73 -0
  74. isar/storage/blob_storage.py +71 -45
  75. isar/storage/local_storage.py +28 -14
  76. isar/storage/storage_interface.py +28 -6
  77. isar/storage/uploader.py +184 -55
  78. isar/storage/utilities.py +35 -27
  79. isar-1.34.9.dist-info/METADATA +496 -0
  80. isar-1.34.9.dist-info/RECORD +135 -0
  81. {isar-1.15.0.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
  82. isar-1.34.9.dist-info/entry_points.txt +3 -0
  83. robot_interface/models/exceptions/__init__.py +0 -7
  84. robot_interface/models/exceptions/robot_exceptions.py +274 -4
  85. robot_interface/models/initialize/__init__.py +0 -1
  86. robot_interface/models/inspection/__init__.py +0 -13
  87. robot_interface/models/inspection/inspection.py +43 -34
  88. robot_interface/models/mission/mission.py +18 -14
  89. robot_interface/models/mission/status.py +20 -25
  90. robot_interface/models/mission/task.py +156 -92
  91. robot_interface/models/robots/battery_state.py +6 -0
  92. robot_interface/models/robots/media.py +13 -0
  93. robot_interface/models/robots/robot_model.py +7 -7
  94. robot_interface/robot_interface.py +135 -66
  95. robot_interface/telemetry/mqtt_client.py +84 -12
  96. robot_interface/telemetry/payloads.py +111 -12
  97. robot_interface/utilities/json_service.py +7 -1
  98. isar/config/predefined_missions/default_turtlebot.json +0 -110
  99. isar/config/predefined_poses/__init__.py +0 -0
  100. isar/config/predefined_poses/predefined_poses.py +0 -616
  101. isar/config/settings.env +0 -26
  102. isar/mission_planner/sequential_task_selector.py +0 -23
  103. isar/mission_planner/task_selector_interface.py +0 -31
  104. isar/models/communication/__init__.py +0 -0
  105. isar/models/communication/message.py +0 -12
  106. isar/models/communication/queues/__init__.py +0 -4
  107. isar/models/communication/queues/queue_io.py +0 -12
  108. isar/models/communication/queues/queue_timeout_error.py +0 -2
  109. isar/models/communication/queues/queues.py +0 -19
  110. isar/models/communication/queues/status_queue.py +0 -20
  111. isar/models/mission_metadata/__init__.py +0 -0
  112. isar/services/readers/__init__.py +0 -0
  113. isar/services/readers/base_reader.py +0 -37
  114. isar/services/service_connections/mqtt/robot_status_publisher.py +0 -93
  115. isar/services/service_connections/stid/__init__.py +0 -0
  116. isar/services/service_connections/stid/stid_service.py +0 -45
  117. isar/services/utilities/queue_utilities.py +0 -39
  118. isar/state_machine/states/idle.py +0 -40
  119. isar/state_machine/states/initialize.py +0 -60
  120. isar/state_machine/states/initiate.py +0 -129
  121. isar/state_machine/states/off.py +0 -18
  122. isar/state_machine/states/stop.py +0 -78
  123. isar/storage/slimm_storage.py +0 -181
  124. isar-1.15.0.dist-info/METADATA +0 -417
  125. isar-1.15.0.dist-info/RECORD +0 -113
  126. robot_interface/models/initialize/initialize_params.py +0 -9
  127. robot_interface/models/mission/step.py +0 -211
  128. {isar-1.15.0.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
  129. {isar-1.15.0.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,80 @@
1
+ from typing import TYPE_CHECKING, List, Optional
2
+
3
+ from isar.apis.models.models import LockdownResponse, MaintenanceResponse
4
+ from isar.config.settings import settings
5
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
6
+ from isar.models.events import Event
7
+ from robot_interface.models.mission.status import RobotStatus
8
+
9
+ if TYPE_CHECKING:
10
+ from isar.state_machine.state_machine import StateMachine
11
+
12
+
13
+ class Recharging(EventHandlerBase):
14
+
15
+ def __init__(self, state_machine: "StateMachine"):
16
+ shared_state = state_machine.shared_state
17
+ events = state_machine.events
18
+
19
+ def robot_battery_level_updated_handler(event: Event[float]):
20
+ battery_level: float = event.check()
21
+ if battery_level < settings.ROBOT_BATTERY_RECHARGE_THRESHOLD:
22
+ return None
23
+
24
+ return state_machine.robot_recharged # type: ignore
25
+
26
+ def robot_offline_handler(event: Event[RobotStatus]):
27
+ robot_status: Optional[RobotStatus] = event.check()
28
+
29
+ if robot_status is None:
30
+ return None
31
+
32
+ if robot_status == RobotStatus.Offline:
33
+ return state_machine.robot_status_offline # type: ignore
34
+
35
+ def _send_to_lockdown_event_handler(event: Event[bool]):
36
+ should_lockdown: bool = event.consume_event()
37
+ if not should_lockdown:
38
+ return None
39
+
40
+ events.api_requests.send_to_lockdown.response.trigger_event(
41
+ LockdownResponse(lockdown_started=True)
42
+ )
43
+ return state_machine.reached_lockdown # type: ignore
44
+
45
+ def _set_maintenance_mode_event_handler(event: Event[bool]):
46
+ should_set_maintenande_mode: bool = event.consume_event()
47
+ if should_set_maintenande_mode:
48
+ events.api_requests.set_maintenance_mode.response.trigger_event(
49
+ MaintenanceResponse(is_maintenance_mode=True)
50
+ )
51
+ return state_machine.set_maintenance_mode # type: ignore
52
+ return None
53
+
54
+ event_handlers: List[EventHandlerMapping] = [
55
+ EventHandlerMapping(
56
+ name="robot_battery_update_event",
57
+ event=shared_state.robot_battery_level,
58
+ handler=robot_battery_level_updated_handler,
59
+ ),
60
+ EventHandlerMapping(
61
+ name="robot_offline_event",
62
+ event=shared_state.robot_status,
63
+ handler=robot_offline_handler,
64
+ ),
65
+ EventHandlerMapping(
66
+ name="send_to_lockdown_event",
67
+ event=events.api_requests.send_to_lockdown.request,
68
+ handler=_send_to_lockdown_event_handler,
69
+ ),
70
+ EventHandlerMapping(
71
+ name="set_maintenance_mode",
72
+ event=events.api_requests.set_maintenance_mode.request,
73
+ handler=_set_maintenance_mode_event_handler,
74
+ ),
75
+ ]
76
+ super().__init__(
77
+ state_name="recharging",
78
+ state_machine=state_machine,
79
+ event_handler_mappings=event_handlers,
80
+ )
@@ -0,0 +1,57 @@
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
2
+
3
+ from isar.apis.models.models import ControlMissionResponse
4
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
5
+ from isar.models.events import Event
6
+ from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
7
+
8
+ if TYPE_CHECKING:
9
+ from isar.state_machine.state_machine import StateMachine
10
+
11
+
12
+ class Resuming(EventHandlerBase):
13
+
14
+ def __init__(self, state_machine: "StateMachine"):
15
+ events = state_machine.events
16
+
17
+ def _failed_resume_event_handler(
18
+ event: Event[ErrorMessage],
19
+ ) -> Optional[Callable]:
20
+ error_message: Optional[ErrorMessage] = event.consume_event()
21
+
22
+ if error_message is None:
23
+ return None
24
+
25
+ state_machine.events.api_requests.resume_mission.response.trigger_event(
26
+ ControlMissionResponse(success=False)
27
+ )
28
+
29
+ return state_machine.mission_resuming_failed # type: ignore
30
+
31
+ def _successful_resume_event_handler(event: Event[bool]) -> Optional[Callable]:
32
+ if not event.consume_event():
33
+ return None
34
+
35
+ state_machine.events.api_requests.resume_mission.response.trigger_event(
36
+ ControlMissionResponse(success=True)
37
+ )
38
+
39
+ return state_machine.mission_resumed # type:ignore
40
+
41
+ event_handlers: List[EventHandlerMapping] = [
42
+ EventHandlerMapping(
43
+ name="failed_resume_event",
44
+ event=events.robot_service_events.mission_failed_to_resume,
45
+ handler=_failed_resume_event_handler,
46
+ ),
47
+ EventHandlerMapping(
48
+ name="successful_resume_event",
49
+ event=events.robot_service_events.mission_successfully_resumed,
50
+ handler=_successful_resume_event_handler,
51
+ ),
52
+ ]
53
+ super().__init__(
54
+ state_name="resuming",
55
+ state_machine=state_machine,
56
+ event_handler_mappings=event_handlers,
57
+ )
@@ -0,0 +1,64 @@
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
2
+
3
+ from isar.apis.models.models import ControlMissionResponse
4
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
5
+ from isar.models.events import Event
6
+ from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
7
+
8
+ if TYPE_CHECKING:
9
+ from isar.state_machine.state_machine import StateMachine
10
+
11
+
12
+ class ResumingReturnHome(EventHandlerBase):
13
+
14
+ def __init__(self, state_machine: "StateMachine"):
15
+ events = state_machine.events
16
+
17
+ def _failed_resume_event_handler(
18
+ event: Event[ErrorMessage],
19
+ ) -> Optional[Callable]:
20
+ error_message: Optional[ErrorMessage] = event.consume_event()
21
+
22
+ if error_message is None:
23
+ return None
24
+
25
+ state_machine.events.api_requests.resume_mission.response.trigger_event(
26
+ ControlMissionResponse(
27
+ success=False,
28
+ failure_reason=(
29
+ getattr(error_message, "error_reason", str(error_message))
30
+ if hasattr(error_message, "error_reason")
31
+ else str(error_message)
32
+ ),
33
+ )
34
+ )
35
+
36
+ return state_machine.return_home_mission_resuming_failed # type: ignore
37
+
38
+ def _successful_resume_event_handler(event: Event[bool]) -> Optional[Callable]:
39
+ if not event.consume_event():
40
+ return None
41
+
42
+ state_machine.events.api_requests.resume_mission.response.trigger_event(
43
+ ControlMissionResponse(success=True)
44
+ )
45
+
46
+ return state_machine.return_home_mission_resumed # type: ignore
47
+
48
+ event_handlers: List[EventHandlerMapping] = [
49
+ EventHandlerMapping(
50
+ name="failed_resume_event",
51
+ event=events.robot_service_events.mission_failed_to_resume,
52
+ handler=_failed_resume_event_handler,
53
+ ),
54
+ EventHandlerMapping(
55
+ name="successful_resume_event",
56
+ event=events.robot_service_events.mission_successfully_resumed,
57
+ handler=_successful_resume_event_handler,
58
+ ),
59
+ ]
60
+ super().__init__(
61
+ state_name="resuming_return_home",
62
+ state_machine=state_machine,
63
+ event_handler_mappings=event_handlers,
64
+ )
@@ -0,0 +1,109 @@
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
2
+
3
+ from isar.apis.models.models import LockdownResponse, MissionStartResponse
4
+ from isar.config.settings import settings
5
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
6
+ from isar.models.events import Event
7
+ from robot_interface.models.mission.mission import Mission
8
+
9
+ if TYPE_CHECKING:
10
+ from isar.state_machine.state_machine import StateMachine
11
+
12
+
13
+ class ReturnHomePaused(EventHandlerBase):
14
+
15
+ def __init__(self, state_machine: "StateMachine"):
16
+ events = state_machine.events
17
+ shared_state = state_machine.shared_state
18
+
19
+ def _robot_battery_level_updated_handler(
20
+ event: Event[float],
21
+ ) -> Optional[Callable]:
22
+ battery_level: float = event.check()
23
+
24
+ if (
25
+ battery_level is None
26
+ or battery_level >= settings.ROBOT_MISSION_BATTERY_START_THRESHOLD
27
+ ):
28
+ return None
29
+
30
+ state_machine.events.state_machine_events.resume_mission.trigger_event(True)
31
+ return state_machine.resume # type: ignore
32
+
33
+ def _start_mission_event_handler(
34
+ event: Event[Mission],
35
+ ) -> Optional[Callable]:
36
+ if not event.has_event():
37
+ return None
38
+
39
+ if not state_machine.battery_level_is_above_mission_start_threshold():
40
+ state_machine.events.api_requests.start_mission.request.consume_event()
41
+ response = MissionStartResponse(
42
+ mission_id=None,
43
+ mission_started=False,
44
+ mission_not_started_reason="Robot battery too low",
45
+ )
46
+ state_machine.events.api_requests.start_mission.response.trigger_event(
47
+ response
48
+ )
49
+ return None
50
+ state_machine.events.state_machine_events.stop_mission.trigger_event(True)
51
+ return state_machine.stop_return_home # type: ignore
52
+
53
+ def _send_to_lockdown_event_handler(
54
+ event: Event[bool],
55
+ ) -> Optional[Callable]:
56
+ should_lockdown: bool = event.consume_event()
57
+ if not should_lockdown:
58
+ return None
59
+
60
+ events.api_requests.send_to_lockdown.response.trigger_event(
61
+ LockdownResponse(lockdown_started=True)
62
+ )
63
+ state_machine.events.state_machine_events.resume_mission.trigger_event(True)
64
+ return state_machine.resume_lockdown # type: ignore
65
+
66
+ def _set_maintenance_mode_event_handler(event: Event[bool]):
67
+ should_set_maintenande_mode: bool = event.consume_event()
68
+ if should_set_maintenande_mode:
69
+ state_machine.logger.warning(
70
+ "Cancelling current mission due to robot going to maintenance mode"
71
+ )
72
+ state_machine.events.state_machine_events.stop_mission.trigger_event(
73
+ True
74
+ )
75
+ return state_machine.stop_due_to_maintenance # type: ignore
76
+ return None
77
+
78
+ event_handlers: List[EventHandlerMapping] = [
79
+ EventHandlerMapping(
80
+ name="resume_return_home_event",
81
+ event=events.api_requests.resume_mission.request,
82
+ handler=lambda event: state_machine.resume if event.consume_event() else None, # type: ignore
83
+ ),
84
+ EventHandlerMapping(
85
+ name="robot_battery_update_event",
86
+ event=shared_state.robot_battery_level,
87
+ handler=_robot_battery_level_updated_handler,
88
+ ),
89
+ EventHandlerMapping(
90
+ name="start_mission_event",
91
+ event=events.api_requests.start_mission.request,
92
+ handler=_start_mission_event_handler,
93
+ ),
94
+ EventHandlerMapping(
95
+ name="send_to_lockdown_event",
96
+ event=events.api_requests.send_to_lockdown.request,
97
+ handler=_send_to_lockdown_event_handler,
98
+ ),
99
+ EventHandlerMapping(
100
+ name="set_maintenance_mode",
101
+ event=events.api_requests.set_maintenance_mode.request,
102
+ handler=_set_maintenance_mode_event_handler,
103
+ ),
104
+ ]
105
+ super().__init__(
106
+ state_name="return_home_paused",
107
+ state_machine=state_machine,
108
+ event_handler_mappings=event_handlers,
109
+ )
@@ -0,0 +1,217 @@
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
2
+
3
+ from isar.apis.models.models import (
4
+ ControlMissionResponse,
5
+ LockdownResponse,
6
+ MissionStartResponse,
7
+ )
8
+ from isar.config.settings import settings
9
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
10
+ from isar.models.events import Event
11
+ from isar.state_machine.utils.common_event_handlers import mission_started_event_handler
12
+ from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
13
+ from robot_interface.models.mission.mission import Mission
14
+ from robot_interface.models.mission.status import MissionStatus
15
+ from robot_interface.models.mission.task import ReturnToHome
16
+
17
+ if TYPE_CHECKING:
18
+ from isar.state_machine.state_machine import StateMachine
19
+
20
+
21
+ class ReturningHome(EventHandlerBase):
22
+
23
+ def __init__(self, state_machine: "StateMachine"):
24
+ self.failed_return_home_attempts: int = 0
25
+ events = state_machine.events
26
+ shared_state = state_machine.shared_state
27
+
28
+ def _pause_mission_event_handler(event: Event[bool]) -> Optional[Callable]:
29
+ if not event.consume_event():
30
+ return None
31
+
32
+ state_machine.events.api_requests.pause_mission.response.trigger_event(
33
+ ControlMissionResponse(success=True)
34
+ )
35
+ state_machine.events.state_machine_events.pause_mission.trigger_event(True)
36
+ return state_machine.pause_return_home # type: ignore
37
+
38
+ def _start_mission_event_handler(
39
+ event: Event[Mission],
40
+ ) -> Optional[Callable]:
41
+ if not event.has_event():
42
+ return None
43
+
44
+ if not state_machine.battery_level_is_above_mission_start_threshold():
45
+ state_machine.events.api_requests.start_mission.request.consume_event()
46
+ response = MissionStartResponse(
47
+ mission_id=None,
48
+ mission_started=False,
49
+ mission_not_started_reason="Robot battery too low",
50
+ )
51
+ state_machine.events.api_requests.start_mission.response.trigger_event(
52
+ response
53
+ )
54
+ return None
55
+
56
+ state_machine.events.state_machine_events.stop_mission.trigger_event(True)
57
+ return state_machine.stop_return_home # type: ignore
58
+
59
+ def _mission_status_event_handler(
60
+ event: Event[MissionStatus],
61
+ ) -> Optional[Callable]:
62
+ mission_status: Optional[MissionStatus] = event.consume_event()
63
+
64
+ if mission_status and mission_status not in [
65
+ MissionStatus.InProgress,
66
+ MissionStatus.NotStarted,
67
+ MissionStatus.Paused,
68
+ ]:
69
+ if mission_status != MissionStatus.Successful:
70
+ self.failed_return_home_attempts += 1
71
+ if (
72
+ self.failed_return_home_attempts
73
+ >= settings.RETURN_HOME_RETRY_LIMIT
74
+ ):
75
+ state_machine.logger.warning(
76
+ f"Failed to return home after {self.failed_return_home_attempts} attempts."
77
+ )
78
+ state_machine.publish_intervention_needed(
79
+ error_message=f"Return home failed after {self.failed_return_home_attempts} attempts."
80
+ )
81
+ state_machine.print_transitions()
82
+ return state_machine.return_home_failed # type: ignore
83
+ else:
84
+ state_machine.start_mission(
85
+ Mission(
86
+ tasks=[ReturnToHome()],
87
+ name="Return Home",
88
+ )
89
+ )
90
+ return None
91
+
92
+ # This clears the current robot status value, so we don't read an outdated value
93
+ state_machine.events.robot_service_events.robot_status_changed.clear_event()
94
+ return state_machine.returned_home # type: ignore
95
+ return None
96
+
97
+ def _send_to_lockdown_event_handler(
98
+ event: Event[bool],
99
+ ) -> Optional[Callable]:
100
+ should_lockdown: bool = event.consume_event()
101
+ if not should_lockdown:
102
+ return None
103
+
104
+ events.api_requests.send_to_lockdown.response.trigger_event(
105
+ LockdownResponse(lockdown_started=True)
106
+ )
107
+ return state_machine.go_to_lockdown # type: ignore
108
+
109
+ def _mission_failed_event_handler(
110
+ event: Event[Optional[ErrorMessage]],
111
+ ) -> Optional[Callable]:
112
+ mission_failed: Optional[ErrorMessage] = event.consume_event()
113
+ if mission_failed is not None:
114
+ state_machine.logger.warning(
115
+ f"Failed to initiate return home because: "
116
+ f"{mission_failed.error_description}"
117
+ )
118
+ state_machine.publish_intervention_needed(
119
+ error_message="Return home failed to initiate."
120
+ )
121
+ state_machine.print_transitions()
122
+ return state_machine.return_home_failed # type: ignore
123
+ return None
124
+
125
+ def _set_maintenance_mode_event_handler(event: Event[bool]):
126
+ should_set_maintenande_mode: bool = event.consume_event()
127
+ if should_set_maintenande_mode:
128
+ state_machine.logger.warning(
129
+ "Cancelling current mission due to robot going to maintenance mode"
130
+ )
131
+ state_machine.events.state_machine_events.stop_mission.trigger_event(
132
+ True
133
+ )
134
+ return state_machine.stop_due_to_maintenance # type: ignore
135
+ return None
136
+
137
+ def _robot_already_home_event_handler(event: Event[bool]):
138
+ already_home: bool = event.consume_event()
139
+ if already_home:
140
+ state_machine.logger.info(
141
+ "Robot reported that it is already home. "
142
+ "Assuming return home mission successful without running."
143
+ )
144
+ return state_machine.returned_home # type: ignore
145
+ return None
146
+
147
+ def _robot_battery_level_updated_handler(
148
+ event: Event[float],
149
+ ) -> Optional[Callable]:
150
+ battery_level: float = event.check()
151
+ if (
152
+ battery_level is None
153
+ or battery_level >= settings.ROBOT_MISSION_BATTERY_START_THRESHOLD
154
+ ):
155
+ return None
156
+
157
+ return state_machine.go_to_recharging # type: ignore
158
+
159
+ event_handlers: List[EventHandlerMapping] = [
160
+ EventHandlerMapping(
161
+ name="pause_mission_event",
162
+ event=events.api_requests.pause_mission.request,
163
+ handler=_pause_mission_event_handler,
164
+ ),
165
+ EventHandlerMapping(
166
+ name="mission_started_event",
167
+ event=events.robot_service_events.mission_started,
168
+ handler=lambda event: mission_started_event_handler(
169
+ state_machine, event
170
+ ),
171
+ ),
172
+ EventHandlerMapping(
173
+ name="mission_failed_event",
174
+ event=events.robot_service_events.mission_failed,
175
+ handler=_mission_failed_event_handler,
176
+ ),
177
+ EventHandlerMapping(
178
+ name="start_mission_event",
179
+ event=events.api_requests.start_mission.request,
180
+ handler=_start_mission_event_handler,
181
+ ),
182
+ EventHandlerMapping(
183
+ name="mission_status_event",
184
+ event=events.robot_service_events.mission_status_updated,
185
+ handler=_mission_status_event_handler,
186
+ ),
187
+ EventHandlerMapping(
188
+ name="robot_battery_update_event",
189
+ event=shared_state.robot_battery_level,
190
+ handler=_robot_battery_level_updated_handler,
191
+ ),
192
+ EventHandlerMapping(
193
+ name="send_to_lockdown_event",
194
+ event=events.api_requests.send_to_lockdown.request,
195
+ handler=_send_to_lockdown_event_handler,
196
+ ),
197
+ EventHandlerMapping(
198
+ name="set_maintenance_mode",
199
+ event=events.api_requests.set_maintenance_mode.request,
200
+ handler=_set_maintenance_mode_event_handler,
201
+ ),
202
+ EventHandlerMapping(
203
+ name="robot_already_home",
204
+ event=events.robot_service_events.robot_already_home,
205
+ handler=_robot_already_home_event_handler,
206
+ ),
207
+ ]
208
+ super().__init__(
209
+ state_name="returning_home",
210
+ state_machine=state_machine,
211
+ event_handler_mappings=event_handlers,
212
+ on_transition=self._reset_failure_counter,
213
+ on_entry=self._reset_failure_counter,
214
+ )
215
+
216
+ def _reset_failure_counter(self):
217
+ self.failed_return_home_attempts = 0
@@ -0,0 +1,61 @@
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
2
+
3
+ from isar.apis.models.models import ControlMissionResponse
4
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
5
+ from isar.models.events import Event
6
+ from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
7
+
8
+ if TYPE_CHECKING:
9
+ from isar.state_machine.state_machine import StateMachine
10
+
11
+
12
+ class Stopping(EventHandlerBase):
13
+
14
+ def __init__(self, state_machine: "StateMachine"):
15
+ events = state_machine.events
16
+
17
+ def _failed_stop_event_handler(
18
+ event: Event[ErrorMessage],
19
+ ) -> Optional[Callable]:
20
+ error_message: Optional[ErrorMessage] = event.consume_event()
21
+ if error_message is None:
22
+ return None
23
+
24
+ stopped_mission_response: ControlMissionResponse = ControlMissionResponse(
25
+ success=False, failure_reason="ISAR failed to stop mission"
26
+ )
27
+ state_machine.events.api_requests.stop_mission.response.trigger_event(
28
+ stopped_mission_response
29
+ )
30
+ return state_machine.mission_stopping_failed # type: ignore
31
+
32
+ def _successful_stop_event_handler(event: Event[bool]) -> Optional[Callable]:
33
+ if not event.consume_event():
34
+ return None
35
+
36
+ state_machine.events.api_requests.stop_mission.response.trigger_event(
37
+ ControlMissionResponse(success=True)
38
+ )
39
+ state_machine.print_transitions()
40
+ if not state_machine.battery_level_is_above_mission_start_threshold():
41
+ state_machine.start_return_home_mission()
42
+ return state_machine.start_return_home_monitoring # type: ignore
43
+ return state_machine.mission_stopped # type: ignore
44
+
45
+ event_handlers: List[EventHandlerMapping] = [
46
+ EventHandlerMapping(
47
+ name="failed_stop_event",
48
+ event=events.robot_service_events.mission_failed_to_stop,
49
+ handler=_failed_stop_event_handler,
50
+ ),
51
+ EventHandlerMapping(
52
+ name="successful_stop_event",
53
+ event=events.robot_service_events.mission_successfully_stopped,
54
+ handler=_successful_stop_event_handler,
55
+ ),
56
+ ]
57
+ super().__init__(
58
+ state_name="stopping",
59
+ state_machine=state_machine,
60
+ event_handler_mappings=event_handlers,
61
+ )
@@ -0,0 +1,61 @@
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
2
+
3
+ from isar.apis.models.models import MaintenanceResponse
4
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
5
+ from isar.models.events import Event
6
+ from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
7
+
8
+ if TYPE_CHECKING:
9
+ from isar.state_machine.state_machine import StateMachine
10
+
11
+
12
+ class StoppingDueToMaintenance(EventHandlerBase):
13
+
14
+ def __init__(self, state_machine: "StateMachine"):
15
+ events = state_machine.events
16
+
17
+ def _failed_stop_event_handler(
18
+ event: Event[ErrorMessage],
19
+ ) -> Optional[Callable]:
20
+ error_message: Optional[ErrorMessage] = event.consume_event()
21
+ if error_message is not None:
22
+ events.api_requests.set_maintenance_mode.response.trigger_event(
23
+ MaintenanceResponse(
24
+ is_maintenance_mode=False,
25
+ failure_reason="Failed to stop ongoing mission",
26
+ )
27
+ )
28
+ state_machine.logger.error(
29
+ f"Failed to stop mission in StoppingDueToMaintenance. Message: {error_message.error_description}"
30
+ )
31
+ return state_machine.mission_stopping_failed # type: ignore
32
+ return None
33
+
34
+ def _successful_stop_event_handler(event: Event[bool]) -> Optional[Callable]:
35
+ if event.consume_event():
36
+ state_machine.publish_mission_aborted(
37
+ "Mission aborted, robot being sent to maintenance", True
38
+ )
39
+ events.api_requests.set_maintenance_mode.response.trigger_event(
40
+ MaintenanceResponse(is_maintenance_mode=True)
41
+ )
42
+ return state_machine.mission_stopped # type: ignore
43
+ return None
44
+
45
+ event_handlers: List[EventHandlerMapping] = [
46
+ EventHandlerMapping(
47
+ name="failed_stop_event",
48
+ event=events.robot_service_events.mission_failed_to_stop,
49
+ handler=_failed_stop_event_handler,
50
+ ),
51
+ EventHandlerMapping(
52
+ name="successful_stop_event",
53
+ event=events.robot_service_events.mission_successfully_stopped,
54
+ handler=_successful_stop_event_handler,
55
+ ),
56
+ ]
57
+ super().__init__(
58
+ state_name="stopping_due_to_maintenance",
59
+ state_machine=state_machine,
60
+ event_handler_mappings=event_handlers,
61
+ )