isar 1.25.9__py3-none-any.whl → 1.26.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 (42) hide show
  1. isar/apis/models/start_mission_definition.py +55 -108
  2. isar/apis/robot_control/robot_controller.py +5 -4
  3. isar/apis/schedule/scheduling_controller.py +4 -32
  4. isar/apis/security/authentication.py +2 -2
  5. isar/config/settings.env +1 -3
  6. isar/config/settings.py +5 -5
  7. isar/models/communication/message.py +0 -4
  8. isar/models/communication/queues/queue_utils.py +27 -0
  9. isar/models/communication/queues/queues.py +23 -5
  10. isar/robot/robot.py +91 -0
  11. isar/robot/robot_start_mission.py +73 -0
  12. isar/robot/robot_status.py +46 -0
  13. isar/robot/robot_task_status.py +92 -0
  14. isar/script.py +7 -0
  15. isar/services/utilities/scheduling_utilities.py +15 -26
  16. isar/state_machine/state_machine.py +94 -183
  17. isar/state_machine/states/blocked_protective_stop.py +7 -19
  18. isar/state_machine/states/idle.py +12 -54
  19. isar/state_machine/states/monitor.py +43 -90
  20. isar/state_machine/states/offline.py +8 -33
  21. isar/state_machine/states/paused.py +1 -1
  22. isar/state_machine/states_enum.py +0 -2
  23. isar/state_machine/transitions/fail_mission.py +13 -0
  24. isar/state_machine/transitions/finish_mission.py +39 -0
  25. isar/state_machine/transitions/pause.py +24 -0
  26. isar/state_machine/transitions/resume.py +27 -0
  27. isar/state_machine/transitions/start_mission.py +73 -0
  28. isar/state_machine/transitions/stop.py +33 -0
  29. isar/state_machine/transitions/utils.py +10 -0
  30. isar/storage/slimm_storage.py +2 -2
  31. {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/METADATA +2 -3
  32. {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/RECORD +40 -30
  33. robot_interface/models/mission/task.py +1 -1
  34. robot_interface/telemetry/mqtt_client.py +0 -1
  35. robot_interface/telemetry/payloads.py +3 -3
  36. robot_interface/utilities/json_service.py +1 -1
  37. isar/state_machine/states/initialize.py +0 -70
  38. isar/state_machine/states/initiate.py +0 -111
  39. {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/LICENSE +0 -0
  40. {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/WHEEL +0 -0
  41. {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/entry_points.txt +0 -0
  42. {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/top_level.txt +0 -0
@@ -12,13 +12,14 @@ from robot_interface.models.mission.task import (
12
12
  TASKS,
13
13
  RecordAudio,
14
14
  ReturnToHome,
15
+ TakeGasMeasurement,
15
16
  TakeImage,
16
17
  TakeThermalImage,
17
18
  TakeThermalVideo,
18
19
  TakeVideo,
19
- TakeGasMeasurement,
20
20
  ZoomDescription,
21
21
  )
22
+ from robot_interface.utilities.uuid_string_factory import uuid4_string
22
23
 
23
24
 
24
25
  class InspectionTypes(str, Enum):
@@ -74,11 +75,11 @@ def to_isar_mission(
74
75
  if not isar_tasks:
75
76
  raise MissionPlannerError("Mission does not contain any valid tasks")
76
77
 
77
- isar_mission_name: str
78
- if start_mission_definition.name:
79
- isar_mission_name = start_mission_definition.name
80
- else:
81
- isar_mission_name = _build_mission_name()
78
+ isar_mission_name: str = (
79
+ start_mission_definition.name
80
+ if start_mission_definition.name
81
+ else _build_mission_name()
82
+ )
82
83
 
83
84
  start_pose = None
84
85
  if start_mission_definition.start_pose:
@@ -106,113 +107,59 @@ def to_inspection_task(task_definition: StartMissionTaskDefinition) -> TASKS:
106
107
  inspection_definition = task_definition.inspection
107
108
 
108
109
  if inspection_definition.type == InspectionTypes.image:
109
- if task_definition.id:
110
- return TakeImage(
111
- id=task_definition.id,
112
- robot_pose=task_definition.pose.to_alitra_pose(),
113
- tag_id=task_definition.tag,
114
- target=task_definition.inspection.inspection_target.to_alitra_position(),
115
- metadata=task_definition.inspection.metadata,
116
- zoom=task_definition.zoom,
117
- )
118
- else:
119
- return TakeImage(
120
- robot_pose=task_definition.pose.to_alitra_pose(),
121
- tag_id=task_definition.tag,
122
- target=task_definition.inspection.inspection_target.to_alitra_position(),
123
- metadata=task_definition.inspection.metadata,
124
- zoom=task_definition.zoom,
125
- )
110
+ return TakeImage(
111
+ id=task_definition.id if task_definition.id else uuid4_string(),
112
+ robot_pose=task_definition.pose.to_alitra_pose(),
113
+ tag_id=task_definition.tag,
114
+ target=task_definition.inspection.inspection_target.to_alitra_position(),
115
+ metadata=task_definition.inspection.metadata,
116
+ zoom=task_definition.zoom,
117
+ )
126
118
  elif inspection_definition.type == InspectionTypes.video:
127
- if task_definition.id:
128
- return TakeVideo(
129
- id=task_definition.id,
130
- robot_pose=task_definition.pose.to_alitra_pose(),
131
- tag_id=task_definition.tag,
132
- target=task_definition.inspection.inspection_target.to_alitra_position(),
133
- duration=inspection_definition.duration,
134
- metadata=task_definition.inspection.metadata,
135
- zoom=task_definition.zoom,
136
- )
137
- else:
138
- return TakeVideo(
139
- robot_pose=task_definition.pose.to_alitra_pose(),
140
- tag_id=task_definition.tag,
141
- target=task_definition.inspection.inspection_target.to_alitra_position(),
142
- duration=inspection_definition.duration,
143
- metadata=task_definition.inspection.metadata,
144
- zoom=task_definition.zoom,
145
- )
119
+ return TakeVideo(
120
+ id=task_definition.id if task_definition.id else uuid4_string(),
121
+ robot_pose=task_definition.pose.to_alitra_pose(),
122
+ tag_id=task_definition.tag,
123
+ target=task_definition.inspection.inspection_target.to_alitra_position(),
124
+ duration=inspection_definition.duration,
125
+ metadata=task_definition.inspection.metadata,
126
+ zoom=task_definition.zoom,
127
+ )
146
128
  elif inspection_definition.type == InspectionTypes.thermal_image:
147
- if task_definition.id:
148
- return TakeThermalImage(
149
- id=task_definition.id,
150
- robot_pose=task_definition.pose.to_alitra_pose(),
151
- tag_id=task_definition.tag,
152
- target=task_definition.inspection.inspection_target.to_alitra_position(),
153
- metadata=task_definition.inspection.metadata,
154
- zoom=task_definition.zoom,
155
- )
156
- else:
157
- return TakeThermalImage(
158
- robot_pose=task_definition.pose.to_alitra_pose(),
159
- tag_id=task_definition.tag,
160
- target=task_definition.inspection.inspection_target.to_alitra_position(),
161
- metadata=task_definition.inspection.metadata,
162
- zoom=task_definition.zoom,
163
- )
129
+ return TakeThermalImage(
130
+ id=task_definition.id if task_definition.id else uuid4_string(),
131
+ robot_pose=task_definition.pose.to_alitra_pose(),
132
+ tag_id=task_definition.tag,
133
+ target=task_definition.inspection.inspection_target.to_alitra_position(),
134
+ metadata=task_definition.inspection.metadata,
135
+ zoom=task_definition.zoom,
136
+ )
164
137
  elif inspection_definition.type == InspectionTypes.thermal_video:
165
- if task_definition.id:
166
- return TakeThermalVideo(
167
- id=task_definition.id,
168
- robot_pose=task_definition.pose.to_alitra_pose(),
169
- tag_id=task_definition.tag,
170
- target=task_definition.inspection.inspection_target.to_alitra_position(),
171
- duration=inspection_definition.duration,
172
- metadata=task_definition.inspection.metadata,
173
- zoom=task_definition.zoom,
174
- )
175
- else:
176
- return TakeThermalVideo(
177
- robot_pose=task_definition.pose.to_alitra_pose(),
178
- tag_id=task_definition.tag,
179
- target=task_definition.inspection.inspection_target.to_alitra_position(),
180
- duration=inspection_definition.duration,
181
- metadata=task_definition.inspection.metadata,
182
- zoom=task_definition.zoom,
183
- )
138
+ return TakeThermalVideo(
139
+ id=task_definition.id if task_definition.id else uuid4_string(),
140
+ robot_pose=task_definition.pose.to_alitra_pose(),
141
+ tag_id=task_definition.tag,
142
+ target=task_definition.inspection.inspection_target.to_alitra_position(),
143
+ duration=inspection_definition.duration,
144
+ metadata=task_definition.inspection.metadata,
145
+ zoom=task_definition.zoom,
146
+ )
184
147
  elif inspection_definition.type == InspectionTypes.audio:
185
- if task_definition.id:
186
- return RecordAudio(
187
- id=task_definition.id,
188
- robot_pose=task_definition.pose.to_alitra_pose(),
189
- tag_id=task_definition.tag,
190
- target=task_definition.inspection.inspection_target.to_alitra_position(),
191
- duration=inspection_definition.duration,
192
- metadata=task_definition.inspection.metadata,
193
- )
194
- else:
195
- return RecordAudio(
196
- robot_pose=task_definition.pose.to_alitra_pose(),
197
- tag_id=task_definition.tag,
198
- target=task_definition.inspection.inspection_target.to_alitra_position(),
199
- duration=inspection_definition.duration,
200
- metadata=task_definition.inspection.metadata,
201
- )
148
+ return RecordAudio(
149
+ id=task_definition.id if task_definition.id else uuid4_string(),
150
+ robot_pose=task_definition.pose.to_alitra_pose(),
151
+ tag_id=task_definition.tag,
152
+ target=task_definition.inspection.inspection_target.to_alitra_position(),
153
+ duration=inspection_definition.duration,
154
+ metadata=task_definition.inspection.metadata,
155
+ )
202
156
  elif inspection_definition.type == InspectionTypes.gas_measurement:
203
- if task_definition.id:
204
- return TakeGasMeasurement(
205
- id=task_definition.id,
206
- robot_pose=task_definition.pose.to_alitra_pose(),
207
- tag_id=task_definition.tag,
208
- metadata=task_definition.inspection.metadata,
209
- )
210
- else:
211
- return TakeGasMeasurement(
212
- robot_pose=task_definition.pose.to_alitra_pose(),
213
- tag_id=task_definition.tag,
214
- metadata=task_definition.inspection.metadata,
215
- )
157
+ return TakeGasMeasurement(
158
+ id=task_definition.id if task_definition.id else uuid4_string(),
159
+ robot_pose=task_definition.pose.to_alitra_pose(),
160
+ tag_id=task_definition.tag,
161
+ metadata=task_definition.inspection.metadata,
162
+ )
216
163
  else:
217
164
  raise ValueError(
218
165
  f"Inspection type '{inspection_definition.type}' not supported"
@@ -1,11 +1,12 @@
1
1
  import logging
2
2
 
3
+ from fastapi import HTTPException
3
4
  from injector import inject
4
5
 
5
6
  from isar.apis.models.models import RobotInfoResponse
6
7
  from isar.config.settings import robot_settings, settings
7
8
  from isar.services.utilities.robot_utilities import RobotUtilities
8
- from fastapi import HTTPException
9
+ from robot_interface.models.robots.media import MediaConfig
9
10
 
10
11
 
11
12
  class RobotController:
@@ -17,8 +18,8 @@ class RobotController:
17
18
  self.robot_utilities: RobotUtilities = robot_utilities
18
19
  self.logger = logging.getLogger("api")
19
20
 
20
- def generate_media_config(self):
21
- media_config = self.robot_utilities.generate_media_config()
21
+ def generate_media_config(self) -> MediaConfig:
22
+ media_config: MediaConfig = self.robot_utilities.generate_media_config()
22
23
  if media_config is None:
23
24
  raise HTTPException(
24
25
  status_code=204,
@@ -26,7 +27,7 @@ class RobotController:
26
27
  )
27
28
  return media_config
28
29
 
29
- def get_info(self):
30
+ def get_info(self) -> RobotInfoResponse:
30
31
  return RobotInfoResponse(
31
32
  robot_package=settings.ROBOT_PACKAGE,
32
33
  isar_id=settings.ISAR_ID,
@@ -8,9 +8,9 @@ from injector import inject
8
8
 
9
9
  from isar.apis.models.models import (
10
10
  ControlMissionResponse,
11
- TaskResponse,
12
11
  InputPose,
13
12
  StartMissionResponse,
13
+ TaskResponse,
14
14
  )
15
15
  from isar.apis.models.start_mission_definition import (
16
16
  StartMissionDefinition,
@@ -45,12 +45,6 @@ class SchedulingController:
45
45
  title="Mission ID",
46
46
  description="ID-number for predefined mission",
47
47
  ),
48
- initial_pose: Optional[InputPose] = Body(
49
- default=None,
50
- description="The starting point of the mission. Used for initial "
51
- "localization of robot",
52
- embed=True,
53
- ),
54
48
  return_pose: Optional[InputPose] = Body(
55
49
  default=None,
56
50
  description="End pose of the mission. The robot return to the specified "
@@ -72,15 +66,9 @@ class SchedulingController:
72
66
  mission=mission, robot_capabilities=robot_settings.CAPABILITIES
73
67
  )
74
68
 
75
- initial_pose_alitra: Optional[Pose] = (
76
- initial_pose.to_alitra_pose() if initial_pose else None
77
- )
78
-
79
69
  self.logger.info(f"Starting mission with ISAR Mission ID: '{mission.id}'")
80
70
 
81
- self.scheduling_utilities.start_mission(
82
- mission=mission, initial_pose=initial_pose_alitra
83
- )
71
+ self.scheduling_utilities.start_mission(mission=mission)
84
72
 
85
73
  return self._api_response(mission)
86
74
 
@@ -92,12 +80,6 @@ class SchedulingController:
92
80
  title="Mission Definition",
93
81
  description="Description of the mission in json format",
94
82
  ),
95
- initial_pose: Optional[InputPose] = Body(
96
- default=None,
97
- description="The starting point of the mission. Used for initial "
98
- "localization of robot",
99
- embed=True,
100
- ),
101
83
  return_pose: Optional[InputPose] = Body(
102
84
  default=None,
103
85
  description="End pose of the mission. The robot return to the specified "
@@ -135,14 +117,8 @@ class SchedulingController:
135
117
  mission=mission, robot_capabilities=robot_settings.CAPABILITIES
136
118
  )
137
119
 
138
- initial_pose_alitra: Optional[Pose] = (
139
- initial_pose.to_alitra_pose() if initial_pose else None
140
- )
141
-
142
120
  self.logger.info(f"Starting mission: {mission.id}")
143
- self.scheduling_utilities.start_mission(
144
- mission=mission, initial_pose=initial_pose_alitra
145
- )
121
+ self.scheduling_utilities.start_mission(mission=mission)
146
122
  return self._api_response(mission)
147
123
 
148
124
  def pause_mission(self) -> ControlMissionResponse:
@@ -152,7 +128,6 @@ class SchedulingController:
152
128
 
153
129
  if state not in [
154
130
  States.Monitor,
155
- States.Initiate,
156
131
  ]:
157
132
  error_message = (
158
133
  f"Conflict - Pause command received in invalid state - State: {state}"
@@ -245,10 +220,7 @@ class SchedulingController:
245
220
  self.logger.info(
246
221
  f"Starting move arm mission with ISAR Mission ID: '{mission.id}'"
247
222
  )
248
- self.scheduling_utilities.start_mission(
249
- mission=mission,
250
- initial_pose=None,
251
- )
223
+ self.scheduling_utilities.start_mission(mission=mission)
252
224
  return self._api_response(mission)
253
225
 
254
226
  def _api_response(self, mission: Mission) -> StartMissionResponse:
@@ -50,7 +50,7 @@ class Authenticator:
50
50
  enabled_string = "enabled" if self.authentication_enabled else "disabled"
51
51
  self.logger.info(f"API authentication is {enabled_string}")
52
52
 
53
- def should_authenticate(self):
53
+ def should_authenticate(self) -> bool:
54
54
  return self.authentication_enabled
55
55
 
56
56
  def get_scheme(self):
@@ -58,7 +58,7 @@ class Authenticator:
58
58
  return validate_has_role
59
59
  return NoSecurity
60
60
 
61
- async def load_config(self):
61
+ async def load_config(self) -> None:
62
62
  """
63
63
  Load OpenID config on startup.
64
64
  """
isar/config/settings.env CHANGED
@@ -1,7 +1,5 @@
1
1
  ISAR_ROBOT_PACKAGE = isar_robot
2
2
 
3
- ISAR_RUN_MISSION_BY_TASK = true
4
-
5
3
  ISAR_STORAGE_LOCAL_ENABLED = true
6
4
  ISAR_STORAGE_BLOB_ENABLED = false
7
5
  ISAR_STORAGE_SLIMM_ENABLED = false
@@ -12,7 +10,7 @@ ISAR_LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED = false
12
10
  ISAR_MQTT_ENABLED = true
13
11
  ISAR_MQTT_SSL_ENABLED = true
14
12
 
15
- ISAR_AUTHENTICATION_ENABLED = false
13
+ ISAR_AUTHENTICATION_ENABLED = true
16
14
 
17
15
  ISAR_PLANT_SHORT_NAME = HUA
18
16
 
isar/config/settings.py CHANGED
@@ -62,7 +62,7 @@ class Settings(BaseSettings):
62
62
  REQUEST_STATUS_COMMUNICATION_RECONNECT_DELAY: float = Field(default=10)
63
63
 
64
64
  # Number of attempts to stop the robot before giving up
65
- STOP_ROBOT_ATTEMPTS_LIMIT: int = Field(default=10)
65
+ STOP_ROBOT_ATTEMPTS_LIMIT: int = Field(default=3)
66
66
 
67
67
  # Number of attempts to stop the robot before giving up
68
68
  UPLOAD_FAILURE_ATTEMPTS_LIMIT: int = Field(default=10)
@@ -111,7 +111,7 @@ class Settings(BaseSettings):
111
111
  # Enabling this requires certain resources available for OAuth2 authentication
112
112
  # Currently supported authentication is Azure AD
113
113
  # (https://github.com/Intility/fastapi-azure-auth)
114
- AUTHENTICATION_ENABLED: bool = Field(default=False)
114
+ AUTHENTICATION_ENABLED: bool = Field(default=True)
115
115
 
116
116
  # Tenant ID for the Azure tenant with your Azure Active Directory application
117
117
  AZURE_TENANT_ID: str = Field(default="3aa4a235-b6e2-48d5-9195-7fcf05b459b0")
@@ -169,13 +169,13 @@ class Settings(BaseSettings):
169
169
  # submitted with the results once they have been uploaded.
170
170
 
171
171
  # Four digit code indicating facility
172
- PLANT_CODE: str = Field(default="1320")
172
+ PLANT_CODE: str = Field(default="1210")
173
173
 
174
174
  # Name of the facility the robot is operating in
175
- PLANT_NAME: str = Field(default="Kårstø")
175
+ PLANT_NAME: str = Field(default="Huldra")
176
176
 
177
177
  # Shortname of the facility the robot is operating in
178
- PLANT_SHORT_NAME: str = Field(default="KAA")
178
+ PLANT_SHORT_NAME: str = Field(default="HUA")
179
179
 
180
180
  # Country the robot is operating in
181
181
  COUNTRY: str = Field(default="Norway")
@@ -1,7 +1,4 @@
1
1
  from dataclasses import dataclass
2
- from typing import Optional
3
-
4
- from alitra import Pose
5
2
 
6
3
  from robot_interface.models.mission.mission import Mission
7
4
 
@@ -9,4 +6,3 @@ from robot_interface.models.mission.mission import Mission
9
6
  @dataclass
10
7
  class StartMissionMessage:
11
8
  mission: Mission
12
- initial_pose: Optional[Pose]
@@ -0,0 +1,27 @@
1
+ import queue
2
+ from typing import Any
3
+
4
+ from isar.models.communication.queues.queue_io import QueueIO
5
+ from isar.models.communication.queues.status_queue import StatusQueue
6
+
7
+
8
+ def trigger_event(queueio: QueueIO, data: Any = None) -> None:
9
+ queueio.input.put(data if data is not None else True)
10
+
11
+
12
+ def check_shared_state(queueio: StatusQueue) -> Any:
13
+ try:
14
+ return queueio.check()
15
+ except queue.Empty:
16
+ return None
17
+
18
+
19
+ def update_shared_state(queueio: StatusQueue, data: Any = None) -> None:
20
+ queueio.update(data if data is not None else True)
21
+
22
+
23
+ def check_for_event(queueio: QueueIO) -> Any:
24
+ try:
25
+ return queueio.input.get(block=False)
26
+ except queue.Empty:
27
+ return None
@@ -7,13 +7,31 @@ from isar.models.communication.queues.status_queue import StatusQueue
7
7
 
8
8
  class Queues:
9
9
  def __init__(self) -> None:
10
- self.start_mission: QueueIO = QueueIO(input_size=1, output_size=1)
11
- self.stop_mission: QueueIO = QueueIO(input_size=1, output_size=1)
12
- self.pause_mission: QueueIO = QueueIO(input_size=1, output_size=1)
13
- self.resume_mission: QueueIO = QueueIO(input_size=1, output_size=1)
14
- self.single_action: QueueIO = QueueIO(input_size=1, output_size=1)
10
+ self.api_start_mission: QueueIO = QueueIO(input_size=1, output_size=1)
11
+ self.api_stop_mission: QueueIO = QueueIO(input_size=1, output_size=1)
12
+ self.api_pause_mission: QueueIO = QueueIO(input_size=1, output_size=1)
13
+ self.api_resume_mission: QueueIO = QueueIO(input_size=1, output_size=1)
14
+
15
+ self.state_machine_start_mission: QueueIO = QueueIO(input_size=1, output_size=1)
16
+ self.state_machine_stop_mission: QueueIO = QueueIO(input_size=1, output_size=1)
17
+ self.state_machine_pause_mission: QueueIO = QueueIO(input_size=1, output_size=1)
18
+ self.state_machine_task_status_request: QueueIO = QueueIO(
19
+ input_size=1, output_size=1
20
+ )
21
+ self.state_machine_robot_status_request: QueueIO = QueueIO(
22
+ input_size=1, output_size=1
23
+ )
24
+ self.state_machine_current_task: StatusQueue = StatusQueue()
25
+
26
+ self.robot_task_status: QueueIO = QueueIO(input_size=1, output_size=1)
27
+ self.robot_task_status_failed: QueueIO = QueueIO(input_size=1, output_size=1)
28
+ self.robot_mission_started: QueueIO = QueueIO(input_size=1, output_size=1)
29
+ self.robot_mission_failed: QueueIO = QueueIO(input_size=1, output_size=1)
30
+ self.robot_status_changed: QueueIO = QueueIO(input_size=1, output_size=1)
31
+
15
32
  self.upload_queue: Queue = Queue(maxsize=10)
16
33
  self.state: StatusQueue = StatusQueue()
34
+ self.robot_status: StatusQueue = StatusQueue()
17
35
 
18
36
  if settings.MQTT_ENABLED:
19
37
  self.mqtt_queue: Queue = Queue()
isar/robot/robot.py ADDED
@@ -0,0 +1,91 @@
1
+ import logging
2
+ from threading import Event
3
+ from typing import Optional
4
+
5
+ from injector import inject
6
+
7
+ from isar.models.communication.queues.queue_io import QueueIO
8
+ from isar.models.communication.queues.queue_utils import check_for_event
9
+ from isar.models.communication.queues.queues import Queues
10
+ from isar.robot.robot_start_mission import RobotStartMissionThread
11
+ from isar.robot.robot_status import RobotStatusThread
12
+ from isar.robot.robot_task_status import RobotTaskStatusThread
13
+ from robot_interface.robot_interface import RobotInterface
14
+
15
+
16
+ class Robot(object):
17
+
18
+ @inject
19
+ def __init__(self, queues: Queues, robot: RobotInterface):
20
+ self.logger = logging.getLogger("robot")
21
+ self.queues: Queues = queues
22
+ self.robot: RobotInterface = robot
23
+ self.start_mission_thread: Optional[RobotStartMissionThread] = None
24
+ self.robot_status_thread: Optional[RobotStatusThread] = None
25
+ self.robot_task_status_thread: Optional[RobotTaskStatusThread] = None
26
+ self.signal_thread_quitting: Event = Event()
27
+
28
+ def stop(self) -> None:
29
+ self.signal_thread_quitting.set()
30
+ if self.robot_status_thread is not None and self.robot_status_thread.is_alive():
31
+ self.robot_status_thread.join()
32
+ if (
33
+ self.robot_task_status_thread is not None
34
+ and self.robot_status_thread.is_alive()
35
+ ):
36
+ self.robot_task_status_thread.join()
37
+ if (
38
+ self.start_mission_thread is not None
39
+ and self.robot_status_thread.is_alive()
40
+ ):
41
+ self.start_mission_thread.join()
42
+ self.robot_status_thread = None
43
+ self.robot_task_status_thread = None
44
+ self.start_mission_thread = None
45
+
46
+ def _check_and_handle_start_mission(self, queue: QueueIO) -> None:
47
+ start_mission = check_for_event(queue)
48
+ if start_mission is not None:
49
+ if (
50
+ self.start_mission_thread is not None
51
+ and self.start_mission_thread.is_alive()
52
+ ):
53
+ self.logger.warning(
54
+ "Attempted to start mission while another mission was starting."
55
+ )
56
+ self.start_mission_thread.join()
57
+ self.start_mission_thread = RobotStartMissionThread(
58
+ self.queues,
59
+ self.robot,
60
+ self.signal_thread_quitting,
61
+ start_mission,
62
+ )
63
+ self.start_mission_thread.start()
64
+
65
+ def _check_and_handle_task_status_request(self, queue: QueueIO) -> None:
66
+ task_id = check_for_event(queue)
67
+ if task_id:
68
+ self.robot_task_status_thread = RobotTaskStatusThread(
69
+ self.queues, self.robot, self.signal_thread_quitting, task_id
70
+ )
71
+ self.robot_task_status_thread.start()
72
+
73
+ def run(self) -> None:
74
+ self.robot_status_thread = RobotStatusThread(
75
+ self.queues, self.robot, self.signal_thread_quitting
76
+ )
77
+ self.robot_status_thread.start()
78
+
79
+ while True:
80
+ if self.signal_thread_quitting.is_set():
81
+ break
82
+
83
+ self._check_and_handle_start_mission(
84
+ self.queues.state_machine_start_mission
85
+ )
86
+
87
+ self._check_and_handle_task_status_request(
88
+ self.queues.state_machine_task_status_request
89
+ )
90
+
91
+ self.logger.info("Exiting robot service main thread")
@@ -0,0 +1,73 @@
1
+ import logging
2
+ from threading import Event, Thread
3
+ from typing import Optional
4
+
5
+ from isar.config.settings import settings
6
+ from isar.models.communication.queues.queue_utils import trigger_event
7
+ from isar.models.communication.queues.queues import Queues
8
+ from isar.services.utilities.threaded_request import ThreadedRequest
9
+ from robot_interface.models.exceptions.robot_exceptions import (
10
+ ErrorMessage,
11
+ RobotException,
12
+ RobotInfeasibleMissionException,
13
+ )
14
+ from robot_interface.models.mission.mission import Mission
15
+ from robot_interface.robot_interface import RobotInterface
16
+
17
+
18
+ class RobotStartMissionThread(Thread):
19
+
20
+ def __init__(
21
+ self,
22
+ queues: Queues,
23
+ robot: RobotInterface,
24
+ signal_thread_quitting: Event,
25
+ mission: Mission,
26
+ ):
27
+ self.logger = logging.getLogger("robot")
28
+ self.queues: Queues = queues
29
+ self.robot: RobotInterface = robot
30
+ self.start_mission_thread: Optional[ThreadedRequest] = None
31
+ self.signal_thread_quitting: Event = signal_thread_quitting
32
+ self.mission = mission
33
+ Thread.__init__(self, name="Robot start mission thread")
34
+
35
+ def run(self):
36
+ retries = 0
37
+ started_mission = False
38
+ try:
39
+ while not started_mission:
40
+ if self.signal_thread_quitting.wait(0):
41
+ return
42
+ try:
43
+ self.robot.initiate_mission(self.mission)
44
+ except RobotException as e:
45
+ retries += 1
46
+ self.logger.warning(
47
+ f"Initiating failed #: {str(retries)} "
48
+ f"because: {e.error_description}"
49
+ )
50
+
51
+ if retries >= settings.INITIATE_FAILURE_COUNTER_LIMIT:
52
+ error_description = (
53
+ f"Mission will be cancelled after failing to initiate "
54
+ f"{settings.INITIATE_FAILURE_COUNTER_LIMIT} times because: "
55
+ f"{e.error_description}"
56
+ )
57
+
58
+ trigger_event(
59
+ self.queues.robot_mission_failed,
60
+ ErrorMessage(
61
+ error_reason=e.error_reason,
62
+ error_description=error_description,
63
+ ),
64
+ )
65
+ started_mission = True
66
+ except RobotInfeasibleMissionException as e:
67
+ trigger_event(
68
+ self.queues.robot_mission_failed,
69
+ ErrorMessage(
70
+ error_reason=e.error_reason, error_description=e.error_description
71
+ ),
72
+ )
73
+ trigger_event(self.queues.robot_mission_started)