isar 1.20.2__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 (124) hide show
  1. isar/apis/api.py +148 -76
  2. isar/apis/models/__init__.py +0 -1
  3. isar/apis/models/models.py +21 -11
  4. isar/apis/models/start_mission_definition.py +110 -168
  5. isar/apis/robot_control/robot_controller.py +41 -0
  6. isar/apis/schedule/scheduling_controller.py +124 -162
  7. isar/apis/security/authentication.py +5 -5
  8. isar/config/certs/ca-cert.pem +33 -31
  9. isar/config/keyvault/keyvault_service.py +1 -1
  10. isar/config/log.py +45 -40
  11. isar/config/logging.conf +16 -31
  12. isar/config/open_telemetry.py +102 -0
  13. isar/config/predefined_mission_definition/default_exr.json +0 -2
  14. isar/config/predefined_mission_definition/default_mission.json +1 -5
  15. isar/config/predefined_mission_definition/default_turtlebot.json +4 -11
  16. isar/config/predefined_missions/default.json +67 -87
  17. isar/config/predefined_missions/default_extra_capabilities.json +107 -0
  18. isar/config/settings.py +76 -111
  19. isar/eventhandlers/eventhandler.py +123 -0
  20. isar/mission_planner/local_planner.py +6 -20
  21. isar/mission_planner/mission_planner_interface.py +1 -1
  22. isar/models/events.py +184 -0
  23. isar/models/status.py +18 -0
  24. isar/modules.py +118 -199
  25. isar/robot/robot.py +377 -0
  26. isar/robot/robot_battery.py +60 -0
  27. isar/robot/robot_monitor_mission.py +357 -0
  28. isar/robot/robot_pause_mission.py +74 -0
  29. isar/robot/robot_resume_mission.py +67 -0
  30. isar/robot/robot_start_mission.py +66 -0
  31. isar/robot/robot_status.py +61 -0
  32. isar/robot/robot_stop_mission.py +68 -0
  33. isar/robot/robot_upload_inspection.py +75 -0
  34. isar/script.py +57 -40
  35. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  36. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
  37. isar/services/service_connections/mqtt/robot_info_publisher.py +3 -3
  38. isar/services/service_connections/persistent_memory.py +69 -0
  39. isar/services/utilities/mqtt_utilities.py +93 -0
  40. isar/services/utilities/robot_utilities.py +20 -0
  41. isar/services/utilities/scheduling_utilities.py +393 -65
  42. isar/state_machine/state_machine.py +219 -538
  43. isar/state_machine/states/__init__.py +0 -8
  44. isar/state_machine/states/await_next_mission.py +114 -0
  45. isar/state_machine/states/blocked_protective_stop.py +60 -0
  46. isar/state_machine/states/going_to_lockdown.py +95 -0
  47. isar/state_machine/states/going_to_recharging.py +92 -0
  48. isar/state_machine/states/home.py +115 -0
  49. isar/state_machine/states/intervention_needed.py +77 -0
  50. isar/state_machine/states/lockdown.py +38 -0
  51. isar/state_machine/states/maintenance.py +36 -0
  52. isar/state_machine/states/monitor.py +137 -247
  53. isar/state_machine/states/offline.py +51 -53
  54. isar/state_machine/states/paused.py +92 -23
  55. isar/state_machine/states/pausing.py +48 -0
  56. isar/state_machine/states/pausing_return_home.py +48 -0
  57. isar/state_machine/states/recharging.py +80 -0
  58. isar/state_machine/states/resuming.py +57 -0
  59. isar/state_machine/states/resuming_return_home.py +64 -0
  60. isar/state_machine/states/return_home_paused.py +109 -0
  61. isar/state_machine/states/returning_home.py +217 -0
  62. isar/state_machine/states/stopping.py +61 -0
  63. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  64. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  65. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  66. isar/state_machine/states/stopping_return_home.py +77 -0
  67. isar/state_machine/states/unknown_status.py +72 -0
  68. isar/state_machine/states_enum.py +21 -5
  69. isar/state_machine/transitions/mission.py +192 -0
  70. isar/state_machine/transitions/return_home.py +106 -0
  71. isar/state_machine/transitions/robot_status.py +80 -0
  72. isar/state_machine/utils/common_event_handlers.py +73 -0
  73. isar/storage/blob_storage.py +70 -52
  74. isar/storage/local_storage.py +25 -12
  75. isar/storage/storage_interface.py +28 -7
  76. isar/storage/uploader.py +174 -55
  77. isar/storage/utilities.py +32 -29
  78. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/METADATA +73 -110
  79. isar-1.34.9.dist-info/RECORD +135 -0
  80. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
  81. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/entry_points.txt +1 -0
  82. robot_interface/models/exceptions/robot_exceptions.py +91 -41
  83. robot_interface/models/initialize/__init__.py +0 -1
  84. robot_interface/models/inspection/__init__.py +0 -13
  85. robot_interface/models/inspection/inspection.py +42 -33
  86. robot_interface/models/mission/mission.py +14 -15
  87. robot_interface/models/mission/status.py +20 -26
  88. robot_interface/models/mission/task.py +154 -121
  89. robot_interface/models/robots/battery_state.py +6 -0
  90. robot_interface/models/robots/media.py +13 -0
  91. robot_interface/models/robots/robot_model.py +7 -7
  92. robot_interface/robot_interface.py +119 -84
  93. robot_interface/telemetry/mqtt_client.py +74 -12
  94. robot_interface/telemetry/payloads.py +91 -13
  95. robot_interface/utilities/json_service.py +7 -1
  96. isar/config/predefined_missions/default_turtlebot.json +0 -110
  97. isar/config/predefined_poses/__init__.py +0 -0
  98. isar/config/predefined_poses/predefined_poses.py +0 -616
  99. isar/config/settings.env +0 -25
  100. isar/mission_planner/sequential_task_selector.py +0 -23
  101. isar/mission_planner/task_selector_interface.py +0 -31
  102. isar/models/communication/__init__.py +0 -0
  103. isar/models/communication/message.py +0 -12
  104. isar/models/communication/queues/__init__.py +0 -4
  105. isar/models/communication/queues/queue_io.py +0 -12
  106. isar/models/communication/queues/queue_timeout_error.py +0 -2
  107. isar/models/communication/queues/queues.py +0 -19
  108. isar/models/communication/queues/status_queue.py +0 -20
  109. isar/models/mission_metadata/__init__.py +0 -0
  110. isar/services/readers/__init__.py +0 -0
  111. isar/services/readers/base_reader.py +0 -37
  112. isar/services/service_connections/stid/__init__.py +0 -0
  113. isar/services/utilities/queue_utilities.py +0 -39
  114. isar/state_machine/states/idle.py +0 -85
  115. isar/state_machine/states/initialize.py +0 -71
  116. isar/state_machine/states/initiate.py +0 -142
  117. isar/state_machine/states/off.py +0 -18
  118. isar/state_machine/states/stop.py +0 -95
  119. isar/storage/slimm_storage.py +0 -191
  120. isar-1.20.2.dist-info/RECORD +0 -116
  121. robot_interface/models/initialize/initialize_params.py +0 -9
  122. robot_interface/models/mission/step.py +0 -234
  123. {isar-1.20.2.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
  124. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
@@ -1,263 +1,153 @@
1
- import logging
2
- import time
3
- from copy import deepcopy
4
- from typing import TYPE_CHECKING, Callable, Optional, Sequence, Tuple, Union
5
-
6
- from injector import inject
7
- from transitions import State
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
8
2
 
3
+ from isar.apis.models.models import ControlMissionResponse
9
4
  from isar.config.settings import settings
10
- from isar.mission_planner.task_selector_interface import TaskSelectorStop
11
- from isar.services.utilities.threaded_request import (
12
- ThreadedRequest,
13
- ThreadedRequestNotFinishedError,
14
- )
15
- from robot_interface.models.exceptions.robot_exceptions import (
16
- ErrorMessage,
17
- RobotCommunicationTimeoutException,
18
- RobotException,
19
- RobotMissionStatusException,
20
- RobotRetrieveInspectionException,
21
- RobotStepStatusException,
5
+ from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
6
+ from isar.models.events import Event
7
+ from isar.state_machine.utils.common_event_handlers import (
8
+ mission_started_event_handler,
9
+ stop_mission_event_handler,
22
10
  )
23
- from robot_interface.models.inspection.inspection import Inspection
24
- from robot_interface.models.mission.mission import Mission
25
- from robot_interface.models.mission.status import MissionStatus, TaskStatus
26
- from robot_interface.models.mission.step import InspectionStep, Step, StepStatus
11
+ from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
12
+ from robot_interface.models.mission.status import MissionStatus
27
13
 
28
14
  if TYPE_CHECKING:
29
15
  from isar.state_machine.state_machine import StateMachine
30
16
 
31
17
 
32
- class Monitor(State):
33
- @inject
34
- def __init__(self, state_machine: "StateMachine") -> None:
35
- super().__init__(name="monitor", on_enter=self.start, on_exit=self.stop)
36
- self.state_machine: "StateMachine" = state_machine
37
- self.request_status_failure_counter: int = 0
38
- self.request_status_failure_counter_limit: int = (
39
- settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT
40
- )
41
-
42
- self.logger = logging.getLogger("state_machine")
43
- self.step_status_thread: Optional[ThreadedRequest] = None
18
+ class Monitor(EventHandlerBase):
44
19
 
45
- def start(self) -> None:
46
- self.state_machine.update_state()
47
- self._run()
20
+ def __init__(self, state_machine: "StateMachine"):
21
+ events = state_machine.events
22
+ shared_state = state_machine.shared_state
48
23
 
49
- def stop(self) -> None:
50
- if self.step_status_thread:
51
- self.step_status_thread.wait_for_thread()
52
- self.step_status_thread = None
24
+ def _pause_mission_event_handler(event: Event[bool]) -> Optional[Callable]:
25
+ if not event.consume_event():
26
+ return None
53
27
 
54
- def _run(self) -> None:
55
- transition: Callable
56
- while True:
57
- if self.state_machine.should_stop_mission():
58
- transition = self.state_machine.stop # type: ignore
59
- break
60
-
61
- if self.state_machine.should_pause_mission():
62
- transition = self.state_machine.pause # type: ignore
63
- break
64
-
65
- if not self.step_status_thread:
66
- self._run_get_status_thread(
67
- status_function=self.state_machine.robot.step_status,
68
- thread_name="State Machine Monitor Get Step Status",
69
- )
70
- try:
71
- status: Union[StepStatus, MissionStatus] = (
72
- self.step_status_thread.get_output()
73
- )
74
- except ThreadedRequestNotFinishedError:
75
- time.sleep(self.state_machine.sleep_time)
76
- continue
77
-
78
- except RobotCommunicationTimeoutException as e:
79
- self.state_machine.current_mission.error_message = ErrorMessage(
80
- error_reason=e.error_reason, error_description=e.error_description
81
- )
82
- self.step_status_thread = None
83
- self.request_status_failure_counter += 1
84
- self.logger.warning(
85
- f"Monitoring step {self.state_machine.current_step.id} failed #: "
86
- f"{self.request_status_failure_counter} failed because: {e.error_description}"
87
- )
88
-
89
- if (
90
- self.request_status_failure_counter
91
- >= self.request_status_failure_counter_limit
92
- ):
93
- self.state_machine.current_step.error_message = ErrorMessage(
94
- error_reason=e.error_reason,
95
- error_description=e.error_description,
96
- )
97
- self.logger.error(
98
- f"Step will be cancelled after failing to get step status "
99
- f"{self.request_status_failure_counter} times because: "
100
- f"{e.error_description}"
101
- )
102
- status = StepStatus.Failed
103
- else:
104
- continue
105
-
106
- except RobotStepStatusException as e:
107
- self.state_machine.current_step.error_message = ErrorMessage(
108
- error_reason=e.error_reason, error_description=e.error_description
109
- )
110
- self.logger.error(
111
- f"Monitoring step {self.state_machine.current_step.id[:8]} failed "
112
- f"because: {e.error_description}"
113
- )
114
- status = StepStatus.Failed
115
-
116
- except RobotMissionStatusException as e:
117
- self.state_machine.current_mission.error_message = ErrorMessage(
118
- error_reason=e.error_reason, error_description=e.error_description
119
- )
120
- self.logger.error(
121
- f"Monitoring mission {self.state_machine.current_mission.id} "
122
- f"failed because: {e.error_description}"
123
- )
124
- status = MissionStatus.Failed
125
-
126
- except RobotException as e:
127
- self._set_error_message(e)
128
- status = StepStatus.Failed
129
-
130
- self.logger.error(
131
- f"Retrieving the status failed because: {e.error_description}"
132
- )
133
-
134
- if isinstance(status, StepStatus):
135
- self.state_machine.current_step.status = status
136
- elif isinstance(status, MissionStatus):
137
- self.state_machine.current_mission.status = status
138
- self.logger.error(
139
- f"Received an invalid status update when monitoring mission. Only StepStatus is expected."
140
- )
141
-
142
- if self._should_upload_inspections():
143
- get_inspections_thread = ThreadedRequest(
144
- self._queue_inspections_for_upload
145
- )
146
- get_inspections_thread.start_thread(
147
- deepcopy(self.state_machine.current_mission),
148
- deepcopy(self.state_machine.current_step),
149
- name="State Machine Get Inspections",
150
- )
151
-
152
- if self.state_machine.stepwise_mission:
153
- if self._step_finished(self.state_machine.current_step):
154
- transition = self.state_machine.step_finished # type: ignore
155
- break
156
- else:
157
- if isinstance(status, StepStatus):
158
- if self._step_finished(self.state_machine.current_step):
159
- self.state_machine.update_current_task()
160
- if self.state_machine.current_task == None:
161
- transition = self.state_machine.full_mission_finished # type: ignore
162
- break
163
- self.state_machine.update_current_step()
164
- self.state_machine.current_task.update_task_status()
165
- else: # If not all steps are done
166
- self.state_machine.current_task.status = TaskStatus.InProgress
167
-
168
- self.state_machine.publish_task_status(
169
- self.state_machine.current_task
170
- )
171
- if self.state_machine.current_task.status == TaskStatus.Successful:
172
- try:
173
- self.state_machine.current_task = (
174
- self.state_machine.task_selector.next_task()
175
- )
176
- except TaskSelectorStop:
177
- # Indicates that all tasks are finished
178
- self.state_machine.current_task = None
179
- transition = self.state_machine.full_mission_finished # type: ignore
180
- break
181
- self.state_machine.update_current_step()
182
- elif self._mission_finished(self.state_machine.current_mission):
183
- transition = self.state_machine.full_mission_finished # type: ignore
184
- break
185
-
186
- self.step_status_thread = None
187
- time.sleep(self.state_machine.sleep_time)
188
-
189
- transition()
190
-
191
- def _run_get_status_thread(
192
- self, status_function: Callable, thread_name: str
193
- ) -> None:
194
- self.step_status_thread = ThreadedRequest(request_func=status_function)
195
- self.step_status_thread.start_thread(name=thread_name)
196
-
197
- def _queue_inspections_for_upload(
198
- self, mission: Mission, current_step: InspectionStep
199
- ) -> None:
200
- try:
201
- inspections: Sequence[Inspection] = (
202
- self.state_machine.robot.get_inspections(step=current_step)
203
- )
204
-
205
- except (RobotRetrieveInspectionException, RobotException) as e:
206
- self._set_error_message(e)
207
- self.logger.error(
208
- f"Failed to retrieve inspections because: {e.error_description}"
28
+ state_machine.events.api_requests.pause_mission.response.trigger_event(
29
+ ControlMissionResponse(success=True)
209
30
  )
210
- return
211
-
212
- if not inspections:
213
- self.logger.warning(
214
- f"No inspection data retrieved for step {str(current_step.id)[:8]}"
215
- )
216
-
217
- for inspection in inspections:
218
- inspection.metadata.tag_id = current_step.tag_id
219
-
220
- message: Tuple[Inspection, Mission] = (
221
- inspection,
222
- mission,
31
+ state_machine.events.state_machine_events.pause_mission.trigger_event(True)
32
+ return state_machine.pause # type: ignore
33
+
34
+ def _robot_battery_level_updated_handler(
35
+ event: Event[float],
36
+ ) -> Optional[Callable]:
37
+ battery_level: float = event.check()
38
+ if (
39
+ battery_level is None
40
+ or battery_level >= settings.ROBOT_MISSION_BATTERY_START_THRESHOLD
41
+ ):
42
+ return None
43
+
44
+ state_machine.logger.warning(
45
+ "Cancelling current mission due to low battery"
223
46
  )
224
- self.state_machine.queues.upload_queue.put(message)
225
- self.logger.info(f"Inspection: {str(inspection.id)[:8]} queued for upload")
226
-
227
- def _step_finished(self, step: Step) -> bool:
228
- finished: bool = False
229
- if step.status == StepStatus.Failed:
230
- self.logger.warning(
231
- f"Step: {str(step.id)[:8]} was reported as failed by the robot"
47
+ state_machine.events.state_machine_events.stop_mission.trigger_event(True)
48
+ return state_machine.stop_go_to_recharge # type: ignore
49
+
50
+ def _send_to_lockdown_event_handler(
51
+ event: Event[bool],
52
+ ) -> Optional[Callable]:
53
+ should_lockdown: bool = event.consume_event()
54
+ if not should_lockdown:
55
+ return None
56
+
57
+ state_machine.logger.warning(
58
+ "Cancelling current mission due to robot going to lockdown"
232
59
  )
233
- finished = True
234
- elif step.status == StepStatus.Successful:
235
- self.logger.info(
236
- f"{type(step).__name__} step: {str(step.id)[:8]} completed"
60
+ state_machine.events.state_machine_events.stop_mission.trigger_event(True)
61
+ return state_machine.stop_go_to_lockdown # type: ignore
62
+
63
+ def _mission_status_event_handler(
64
+ event: Event[MissionStatus],
65
+ ) -> Optional[Callable]:
66
+ mission_status: Optional[MissionStatus] = event.consume_event()
67
+ if mission_status:
68
+ if mission_status not in [
69
+ MissionStatus.InProgress,
70
+ MissionStatus.NotStarted,
71
+ MissionStatus.Paused,
72
+ ]:
73
+ state_machine.logger.info(
74
+ f"Mission completed with status {mission_status}"
75
+ )
76
+ state_machine.print_transitions()
77
+ return state_machine.mission_finished # type: ignore
78
+ return None
79
+
80
+ def _set_maintenance_mode_event_handler(event: Event[bool]):
81
+ should_set_maintenande_mode: bool = event.consume_event()
82
+ if should_set_maintenande_mode:
83
+ state_machine.logger.warning(
84
+ "Cancelling current mission due to robot going to maintenance mode"
85
+ )
86
+ state_machine.events.state_machine_events.stop_mission.trigger_event(
87
+ True
88
+ )
89
+ return state_machine.stop_due_to_maintenance # type: ignore
90
+ return None
91
+
92
+ def _mission_failed_event_handler(
93
+ event: Event[Optional[ErrorMessage]],
94
+ ) -> Optional[Callable]:
95
+ mission_failed: Optional[ErrorMessage] = event.consume_event()
96
+ if mission_failed is None:
97
+ return None
98
+
99
+ state_machine.logger.warning(
100
+ f"Failed to initiate mission because: "
101
+ f"{mission_failed.error_description}"
237
102
  )
238
- finished = True
239
- return finished
240
-
241
- @staticmethod
242
- def _mission_finished(mission: Mission) -> bool:
243
- if (
244
- mission.status == MissionStatus.Successful
245
- or mission.status == MissionStatus.PartiallySuccessful
246
- or mission.status == MissionStatus.Failed
247
- ):
248
- return True
249
- return False
250
-
251
- def _should_upload_inspections(self) -> bool:
252
- step: Step = self.state_machine.current_step
253
- return (
254
- self._step_finished(step)
255
- and step.status == StepStatus.Successful
256
- and isinstance(step, InspectionStep)
257
- )
258
-
259
- def _set_error_message(self, e: RobotException) -> None:
260
- error_message: ErrorMessage = ErrorMessage(
261
- error_reason=e.error_reason, error_description=e.error_description
103
+ return state_machine.mission_failed_to_start # type: ignore
104
+
105
+ event_handlers: List[EventHandlerMapping] = [
106
+ EventHandlerMapping(
107
+ name="stop_mission_event",
108
+ event=events.api_requests.stop_mission.request,
109
+ handler=lambda event: stop_mission_event_handler(state_machine, event),
110
+ ),
111
+ EventHandlerMapping(
112
+ name="pause_mission_event",
113
+ event=events.api_requests.pause_mission.request,
114
+ handler=_pause_mission_event_handler,
115
+ ),
116
+ EventHandlerMapping(
117
+ name="mission_started_event",
118
+ event=events.robot_service_events.mission_started,
119
+ handler=lambda event: mission_started_event_handler(
120
+ state_machine, event
121
+ ),
122
+ ),
123
+ EventHandlerMapping(
124
+ name="mission_failed_event",
125
+ event=events.robot_service_events.mission_failed,
126
+ handler=_mission_failed_event_handler,
127
+ ),
128
+ EventHandlerMapping(
129
+ name="mission_status_event",
130
+ event=events.robot_service_events.mission_status_updated,
131
+ handler=_mission_status_event_handler,
132
+ ),
133
+ EventHandlerMapping(
134
+ name="robot_battery_update_event",
135
+ event=shared_state.robot_battery_level,
136
+ handler=_robot_battery_level_updated_handler,
137
+ ),
138
+ EventHandlerMapping(
139
+ name="send_to_lockdown_event",
140
+ event=events.api_requests.send_to_lockdown.request,
141
+ handler=_send_to_lockdown_event_handler,
142
+ ),
143
+ EventHandlerMapping(
144
+ name="set_maintenance_mode",
145
+ event=events.api_requests.set_maintenance_mode.request,
146
+ handler=_set_maintenance_mode_event_handler,
147
+ ),
148
+ ]
149
+ super().__init__(
150
+ state_name="monitor",
151
+ state_machine=state_machine,
152
+ event_handler_mappings=event_handlers,
262
153
  )
263
- self.state_machine.current_step.error_message = error_message
@@ -1,62 +1,60 @@
1
- import logging
2
- import time
3
- from typing import TYPE_CHECKING, Optional
4
-
5
- from transitions import State
6
-
7
- from isar.config.settings import settings
8
- from isar.services.utilities.threaded_request import (
9
- ThreadedRequest,
10
- ThreadedRequestNotFinishedError,
11
- )
12
- from robot_interface.models.exceptions.robot_exceptions import RobotException
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
13
6
  from robot_interface.models.mission.status import RobotStatus
14
7
 
15
8
  if TYPE_CHECKING:
16
9
  from isar.state_machine.state_machine import StateMachine
17
10
 
18
11
 
19
- class Offline(State):
20
- def __init__(self, state_machine: "StateMachine") -> None:
21
- super().__init__(name="offline", on_enter=self.start, on_exit=self.stop)
22
- self.state_machine: "StateMachine" = state_machine
23
- self.logger = logging.getLogger("state_machine")
24
- self.robot_status_thread: Optional[ThreadedRequest] = None
25
-
26
- def start(self) -> None:
27
- self.state_machine.update_state()
28
- self._run()
12
+ class Offline(EventHandlerBase):
29
13
 
30
- def stop(self) -> None:
31
- if self.robot_status_thread:
32
- self.robot_status_thread.wait_for_thread()
33
- self.robot_status_thread = None
14
+ def __init__(self, state_machine: "StateMachine"):
15
+ events = state_machine.events
16
+ shared_state = state_machine.shared_state
34
17
 
35
- def _run(self) -> None:
36
- while True:
37
- if not self.robot_status_thread:
38
- self.robot_status_thread = ThreadedRequest(
39
- request_func=self.state_machine.robot.robot_status
40
- )
41
- self.robot_status_thread.start_thread(
42
- name="State Machine Offline Get Robot Status"
18
+ def _set_maintenance_mode_event_handler(event: Event[bool]):
19
+ should_set_maintenande_mode: bool = event.consume_event()
20
+ if should_set_maintenande_mode:
21
+ events.api_requests.set_maintenance_mode.response.trigger_event(
22
+ MaintenanceResponse(is_maintenance_mode=True)
43
23
  )
44
-
45
- try:
46
- robot_status: RobotStatus = self.robot_status_thread.get_output()
47
- except ThreadedRequestNotFinishedError:
48
- time.sleep(self.state_machine.sleep_time)
49
- continue
50
-
51
- except (RobotException,) as e:
52
- self.logger.error(
53
- f"Failed to get robot status because: {e.error_description}"
54
- )
55
-
56
- if robot_status != RobotStatus.Offline:
57
- transition = self.state_machine.robot_turned_online # type: ignore
58
- break
59
-
60
- time.sleep(settings.ROBOT_API_STATUS_POLL_INTERVAL)
61
-
62
- transition()
24
+ return state_machine.set_maintenance_mode # type: ignore
25
+ return None
26
+
27
+ def _robot_status_event_handler(
28
+ status_changed_event: Event[bool],
29
+ ) -> Optional[Callable]:
30
+ has_changed = status_changed_event.consume_event()
31
+ if not has_changed:
32
+ return None
33
+ robot_status: Optional[RobotStatus] = shared_state.robot_status.check()
34
+ if robot_status == RobotStatus.Offline:
35
+ return None
36
+ elif robot_status == RobotStatus.Home:
37
+ return state_machine.robot_status_home # type: ignore
38
+ elif robot_status == RobotStatus.Available:
39
+ return state_machine.robot_status_available # type: ignore
40
+ elif robot_status == RobotStatus.BlockedProtectiveStop:
41
+ return state_machine.robot_status_blocked_protective_stop # type: ignore
42
+ return state_machine.robot_status_unknown # type: ignore
43
+
44
+ event_handlers: List[EventHandlerMapping] = [
45
+ EventHandlerMapping(
46
+ name="robot_status_event",
47
+ event=events.robot_service_events.robot_status_changed,
48
+ handler=_robot_status_event_handler,
49
+ ),
50
+ EventHandlerMapping(
51
+ name="set_maintenance_mode",
52
+ event=events.api_requests.set_maintenance_mode.request,
53
+ handler=_set_maintenance_mode_event_handler,
54
+ ),
55
+ ]
56
+ super().__init__(
57
+ state_name="offline",
58
+ state_machine=state_machine,
59
+ event_handler_mappings=event_handlers,
60
+ )
@@ -1,34 +1,103 @@
1
- import logging
2
- import time
3
- from typing import TYPE_CHECKING, Callable
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
4
2
 
5
- from transitions import State
3
+ from isar.config.settings import settings
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 stop_mission_event_handler
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from isar.state_machine.state_machine import StateMachine
9
10
 
10
11
 
11
- class Paused(State):
12
- def __init__(self, state_machine: "StateMachine") -> None:
13
- super().__init__(name="paused", on_enter=self.start)
14
- self.state_machine: "StateMachine" = state_machine
15
- self.logger = logging.getLogger("state_machine")
12
+ class Paused(EventHandlerBase):
16
13
 
17
- def start(self) -> None:
18
- self.state_machine.update_state()
19
- self._run()
14
+ def __init__(self, state_machine: "StateMachine"):
15
+ events = state_machine.events
16
+ shared_state = state_machine.shared_state
20
17
 
21
- def _run(self) -> None:
22
- transition: Callable
23
- while True:
24
- if self.state_machine.should_stop_mission():
25
- transition = self.state_machine.mission_stopped # type: ignore
26
- break
18
+ def _robot_battery_level_updated_handler(
19
+ event: Event[float],
20
+ ) -> Optional[Callable]:
21
+ battery_level: float = event.check()
22
+ if (
23
+ battery_level is None
24
+ or battery_level >= settings.ROBOT_MISSION_BATTERY_START_THRESHOLD
25
+ ):
26
+ return None
27
27
 
28
- if self.state_machine.should_resume_mission():
29
- transition = self.state_machine.resume # type: ignore
30
- break
28
+ state_machine.publish_mission_aborted(
29
+ "Robot battery too low to continue mission", True
30
+ )
31
+ state_machine.print_transitions()
32
+ state_machine.logger.warning(
33
+ "Cancelling current mission due to low battery"
34
+ )
35
+ state_machine.events.state_machine_events.stop_mission.trigger_event(True)
36
+ return state_machine.stop # type: ignore
31
37
 
32
- time.sleep(self.state_machine.sleep_time)
38
+ def _send_to_lockdown_event_handler(
39
+ event: Event[bool],
40
+ ) -> Optional[Callable]:
41
+ should_lockdown: bool = event.consume_event()
42
+ if not should_lockdown:
43
+ return None
33
44
 
34
- transition()
45
+ state_machine.print_transitions()
46
+ state_machine.logger.warning(
47
+ "Cancelling current mission due to robot going to lockdown"
48
+ )
49
+ state_machine.events.state_machine_events.stop_mission.trigger_event(True)
50
+ return state_machine.stop_go_to_lockdown # type: ignore
51
+
52
+ def _set_maintenance_mode_event_handler(event: Event[bool]):
53
+ should_set_maintenande_mode: bool = event.consume_event()
54
+ if should_set_maintenande_mode:
55
+ state_machine.logger.warning(
56
+ "Cancelling current mission due to robot going to maintenance mode"
57
+ )
58
+ state_machine.events.state_machine_events.stop_mission.trigger_event(
59
+ True
60
+ )
61
+ return state_machine.stop_due_to_maintenance # type: ignore
62
+ return None
63
+
64
+ def _resume_misison_event_handler(event: Event[bool]):
65
+ if event.consume_event():
66
+ state_machine.events.state_machine_events.resume_mission.trigger_event(
67
+ True
68
+ )
69
+ return state_machine.resume # type: ignore
70
+ return None
71
+
72
+ event_handlers: List[EventHandlerMapping] = [
73
+ EventHandlerMapping(
74
+ name="stop_mission_event",
75
+ event=events.api_requests.stop_mission.request,
76
+ handler=lambda event: stop_mission_event_handler(state_machine, event),
77
+ ),
78
+ EventHandlerMapping(
79
+ name="resume_mission_event",
80
+ event=events.api_requests.resume_mission.request,
81
+ handler=_resume_misison_event_handler,
82
+ ),
83
+ EventHandlerMapping(
84
+ name="robot_battery_update_event",
85
+ event=shared_state.robot_battery_level,
86
+ handler=_robot_battery_level_updated_handler,
87
+ ),
88
+ EventHandlerMapping(
89
+ name="send_to_lockdown_event",
90
+ event=events.api_requests.send_to_lockdown.request,
91
+ handler=_send_to_lockdown_event_handler,
92
+ ),
93
+ EventHandlerMapping(
94
+ name="set_maintenance_mode",
95
+ event=events.api_requests.set_maintenance_mode.request,
96
+ handler=_set_maintenance_mode_event_handler,
97
+ ),
98
+ ]
99
+ super().__init__(
100
+ state_name="paused",
101
+ state_machine=state_machine,
102
+ event_handler_mappings=event_handlers,
103
+ )