isar 1.10.14__py3-none-any.whl → 1.12.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 (47) hide show
  1. isar/apis/api.py +54 -4
  2. isar/apis/models/start_mission_definition.py +44 -35
  3. isar/apis/security/authentication.py +4 -5
  4. isar/config/keyvault/keyvault_service.py +38 -21
  5. isar/config/log.py +42 -13
  6. isar/config/predefined_mission_definition/__init__.py +0 -0
  7. isar/config/predefined_mission_definition/default_mission.json +98 -0
  8. isar/config/predefined_mission_definition/default_turtlebot.json +136 -0
  9. isar/config/predefined_missions/default.json +84 -84
  10. isar/config/settings.env +5 -0
  11. isar/config/settings.py +51 -10
  12. isar/mission_planner/echo_planner.py +1 -1
  13. isar/models/communication/queues/queues.py +1 -1
  14. isar/models/mission/status.py +5 -5
  15. isar/models/mission_metadata/mission_metadata.py +2 -0
  16. isar/modules.py +3 -2
  17. isar/services/service_connections/mqtt/mqtt_client.py +0 -18
  18. isar/services/service_connections/mqtt/robot_info_publisher.py +33 -0
  19. isar/services/service_connections/mqtt/robot_status_publisher.py +66 -0
  20. isar/services/service_connections/request_handler.py +1 -1
  21. isar/services/utilities/scheduling_utilities.py +5 -5
  22. isar/services/utilities/threaded_request.py +3 -3
  23. isar/state_machine/state_machine.py +13 -5
  24. isar/state_machine/states/idle.py +6 -6
  25. isar/state_machine/states/initialize.py +9 -8
  26. isar/state_machine/states/initiate_step.py +16 -16
  27. isar/state_machine/states/monitor.py +17 -11
  28. isar/state_machine/states/paused.py +6 -6
  29. isar/state_machine/states/stop_step.py +10 -10
  30. isar/state_machine/states_enum.py +0 -1
  31. isar/storage/local_storage.py +2 -2
  32. isar/storage/slimm_storage.py +107 -41
  33. isar/storage/uploader.py +4 -5
  34. isar/storage/utilities.py +1 -23
  35. {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/LICENSE +0 -0
  36. {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/METADATA +4 -1
  37. {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/RECORD +47 -40
  38. {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/WHEEL +0 -0
  39. {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/top_level.txt +0 -0
  40. robot_interface/models/inspection/inspection.py +3 -22
  41. robot_interface/models/mission/status.py +6 -1
  42. robot_interface/models/mission/step.py +5 -32
  43. robot_interface/models/robots/__init__.py +0 -0
  44. robot_interface/models/robots/robot_model.py +13 -0
  45. robot_interface/robot_interface.py +17 -0
  46. robot_interface/telemetry/payloads.py +34 -0
  47. robot_interface/utilities/json_service.py +3 -0
@@ -1,92 +1,92 @@
1
1
  {
2
- "id": 1,
3
- "tasks": [
4
- {
5
- "steps": [
2
+ "id": 1,
3
+ "tasks": [
6
4
  {
7
- "type": "drive_to_pose",
8
- "pose": {
9
- "position": {
10
- "x": -2,
11
- "y": -2,
12
- "z": 0,
13
- "frame": "asset"
14
- },
15
- "orientation": {
16
- "x": 0,
17
- "y": 0,
18
- "z": 0.4794255,
19
- "w": 0.8775826,
20
- "frame": "asset"
21
- },
22
- "frame": "asset"
23
- }
5
+ "steps": [
6
+ {
7
+ "type": "drive_to_pose",
8
+ "pose": {
9
+ "position": {
10
+ "x": -2,
11
+ "y": -2,
12
+ "z": 0,
13
+ "frame": "asset"
14
+ },
15
+ "orientation": {
16
+ "x": 0,
17
+ "y": 0,
18
+ "z": 0.4794255,
19
+ "w": 0.8775826,
20
+ "frame": "asset"
21
+ },
22
+ "frame": "asset"
23
+ }
24
+ },
25
+ {
26
+ "type": "take_image",
27
+ "target": {
28
+ "x": 2,
29
+ "y": 2,
30
+ "z": 0,
31
+ "frame": "robot"
32
+ }
33
+ }
34
+ ]
24
35
  },
25
36
  {
26
- "type": "take_image",
27
- "target": {
28
- "x": 2,
29
- "y": 2,
30
- "z": 0,
31
- "frame": "robot"
32
- }
33
- }
34
- ]
35
- },
36
- {
37
- "steps": [
38
- {
39
- "type": "drive_to_pose",
40
- "pose": {
41
- "position": {
42
- "x": -2,
43
- "y": 2,
44
- "z": 0,
45
- "frame": "asset"
46
- },
47
- "orientation": {
48
- "x": 0,
49
- "y": 0,
50
- "z": 0.4794255,
51
- "w": 0.8775826,
52
- "frame": "asset"
53
- },
54
- "frame": "asset"
55
- }
37
+ "steps": [
38
+ {
39
+ "type": "drive_to_pose",
40
+ "pose": {
41
+ "position": {
42
+ "x": -2,
43
+ "y": 2,
44
+ "z": 0,
45
+ "frame": "asset"
46
+ },
47
+ "orientation": {
48
+ "x": 0,
49
+ "y": 0,
50
+ "z": 0.4794255,
51
+ "w": 0.8775826,
52
+ "frame": "asset"
53
+ },
54
+ "frame": "asset"
55
+ }
56
+ },
57
+ {
58
+ "type": "take_thermal_image",
59
+ "target": {
60
+ "x": 2,
61
+ "y": 2,
62
+ "z": 0,
63
+ "frame": "robot"
64
+ }
65
+ }
66
+ ]
56
67
  },
57
68
  {
58
- "type": "take_thermal_image",
59
- "target": {
60
- "x": 2,
61
- "y": 2,
62
- "z": 0,
63
- "frame": "robot"
64
- }
65
- }
66
- ]
67
- },
68
- {
69
- "steps": [
70
- {
71
- "type": "drive_to_pose",
72
- "pose": {
73
- "position": {
74
- "x": 2,
75
- "y": 2,
76
- "z": 0,
77
- "frame": "asset"
78
- },
79
- "orientation": {
80
- "x": 0,
81
- "y": 0,
82
- "z": 0.4794255,
83
- "w": 0.8775826,
84
- "frame": "asset"
85
- },
86
- "frame": "asset"
87
- }
69
+ "steps": [
70
+ {
71
+ "type": "drive_to_pose",
72
+ "pose": {
73
+ "position": {
74
+ "x": 2,
75
+ "y": 2,
76
+ "z": 0,
77
+ "frame": "asset"
78
+ },
79
+ "orientation": {
80
+ "x": 0,
81
+ "y": 0,
82
+ "z": 0.4794255,
83
+ "w": 0.8775826,
84
+ "frame": "asset"
85
+ },
86
+ "frame": "asset"
87
+ }
88
+ }
89
+ ]
88
90
  }
89
- ]
90
- }
91
- ]
91
+ ]
92
92
  }
isar/config/settings.env CHANGED
@@ -6,6 +6,9 @@ ISAR_STORAGE_LOCAL_ENABLED = true
6
6
  ISAR_STORAGE_BLOB_ENABLED = false
7
7
  ISAR_STORAGE_SLIMM_ENABLED = false
8
8
 
9
+ ISAR_LOG_HANDLER_LOCAL_ENABLED = true
10
+ ISAR_LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED = false
11
+
9
12
  ISAR_MQTT_ENABLED = true
10
13
  ISAR_MQTT_SSL_ENABLED = true
11
14
 
@@ -19,3 +22,5 @@ ISAR_API_HOST_VIEWED_EXTERNALLY = 0.0.0.0
19
22
  ISAR_MQTT_USERNAME = isar
20
23
  ISAR_MQTT_HOST = localhost
21
24
  ISAR_MQTT_PORT = 1883
25
+
26
+ ISAR_KEYVAULT_NAME = IsarDevKv
isar/config/settings.py CHANGED
@@ -5,6 +5,8 @@ from typing import List
5
5
  from pydantic import BaseSettings, Field, validator
6
6
 
7
7
  from isar.config import predefined_missions
8
+ from robot_interface.models.robots.robot_model import RobotModel
9
+ from robot_interface.telemetry.payloads import VideoStream
8
10
 
9
11
 
10
12
  class Settings(BaseSettings):
@@ -50,6 +52,11 @@ class Settings(BaseSettings):
50
52
  # Number of attempts to stop the robot before giving up
51
53
  UPLOAD_FAILURE_MAX_WAIT: int = Field(default=60)
52
54
 
55
+ # ISAR telemetry intervals
56
+ ROBOT_STATUS_PUBLISH_INTERVAL: int = Field(default=1)
57
+ ROBOT_INFO_PUBLISH_INTERVAL: int = Field(default=5)
58
+ ROBOT_API_STATUS_POLL_INTERVAL: int = Field(default=5)
59
+
53
60
  # FastAPI host
54
61
  API_HOST_VIEWED_EXTERNALLY: str = Field(default="0.0.0.0")
55
62
 
@@ -112,7 +119,7 @@ class Settings(BaseSettings):
112
119
  MQTT_PORT: int = Field(default=1883)
113
120
 
114
121
  # Keyvault name
115
- KEYVAULT: str = Field(default="EqRobotKeyVault")
122
+ KEYVAULT_NAME: str = Field(default="IsarDevKv")
116
123
 
117
124
  # URL to storage account for Azure Blob Storage
118
125
  BLOB_STORAGE_ACCOUNT_URL: str = Field(
@@ -144,14 +151,14 @@ class Settings(BaseSettings):
144
151
  ECHO_API_URL: str = Field(default="https://echohubapi.equinor.com/api")
145
152
 
146
153
  # Client ID for SLIMM App Registration
147
- SLIMM_CLIENT_ID: str = Field(default="94c048cc-58e9-4570-85c0-4028c50ab6f3")
154
+ SLIMM_CLIENT_ID: str = Field(default="c630ca4d-d8d6-45ab-8cc6-68a363d0de9e")
148
155
 
149
156
  # Scope for access to SLIMM Ingestion API
150
157
  SLIMM_APP_SCOPE: str = Field(default=".default")
151
158
 
152
159
  # URL for SLIMM endpoint
153
160
  SLIMM_API_URL: str = Field(
154
- default="https://slimmingestapitest.azurewebsites.net/SpatialIngest"
161
+ default="https://scinspectioningestapitest.azurewebsites.net/Ingest"
155
162
  )
156
163
 
157
164
  # Whether the results should be copied directly into the SLIMM datalake or only the
@@ -179,6 +186,25 @@ class Settings(BaseSettings):
179
186
  # Name or unique ID of robot
180
187
  ROBOT_ID: str = Field(default="R2-D2")
181
188
 
189
+ # Serial number of the robot ISAR is connected to
190
+ SERIAL_NUMBER: str = Field(default="0001")
191
+
192
+ # Endpoints to reach video streams for the robot
193
+ VIDEO_STREAMS: List[VideoStream] = Field(
194
+ default=[
195
+ VideoStream(
196
+ name="Front camera",
197
+ url="http://localhost:5000/videostream/front",
198
+ type="turtlebot",
199
+ ),
200
+ VideoStream(
201
+ name="Rear camera",
202
+ url="http://localhost:5000/videostream/rear",
203
+ type="turtlebot",
204
+ ),
205
+ ]
206
+ )
207
+
182
208
  # Data scheme the robot should adhere to
183
209
  # Options [DS0001]
184
210
  DATA_SCHEME: str = Field(default="DS0001")
@@ -203,17 +229,25 @@ class Settings(BaseSettings):
203
229
  DATA_CLASSIFICATION: str = Field(default="internal")
204
230
 
205
231
  # List of MQTT Topics
206
-
207
- TOPIC_ISAR_ROBOT: str = Field(default="robot")
208
-
209
232
  TOPIC_ISAR_STATE: str = Field(default="state")
210
-
211
233
  TOPIC_ISAR_MISSION: str = Field(default="mission")
212
-
213
234
  TOPIC_ISAR_TASK: str = Field(default="task")
214
-
215
235
  TOPIC_ISAR_STEP: str = Field(default="step")
236
+ TOPIC_ISAR_ROBOT_STATUS: str = Field(default="robot_status")
237
+ TOPIC_ISAR_ROBOT_INFO: str = Field(default="robot_info")
238
+
239
+ # Logging
216
240
 
241
+ # Log handlers
242
+ # Determines which log handlers are used by ISAR
243
+ # Multiple log handlers can be chosen
244
+ # Each handler will be called when logging
245
+ # Selecting a different log handler than local may require certain access rights:
246
+ # - The Azure AI logger requires the 'APPLICATIONINSIGHTS_CONNECTION_STRING' to be set as an environment variable.
247
+ LOG_HANDLER_LOCAL_ENABLED: bool = Field(default=True)
248
+ LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED: bool = Field(default=False)
249
+
250
+ # Log levels
217
251
  API_LOG_LEVEL: str = Field(default="INFO")
218
252
  MAIN_LOG_LEVEL: str = Field(default="INFO")
219
253
  MQTT_LOG_LEVEL: str = Field(default="INFO")
@@ -243,11 +277,12 @@ class Settings(BaseSettings):
243
277
  }
244
278
 
245
279
  @validator(
246
- "TOPIC_ISAR_ROBOT",
247
280
  "TOPIC_ISAR_STATE",
248
281
  "TOPIC_ISAR_MISSION",
249
282
  "TOPIC_ISAR_TASK",
250
283
  "TOPIC_ISAR_STEP",
284
+ "TOPIC_ISAR_ROBOT_STATUS",
285
+ "TOPIC_ISAR_ROBOT_INFO",
251
286
  pre=True,
252
287
  always=True,
253
288
  )
@@ -278,8 +313,14 @@ class RobotSettings(BaseSettings):
278
313
  env_file_path = None
279
314
  super().__init__(_env_file=env_file_path)
280
315
 
316
+ # ISAR steps the robot is capable of performing
317
+ # This should be set in the robot package settings.env file
281
318
  CAPABILITIES: List[str] = Field(default=["drive_to_pose", "take_image"])
282
319
 
320
+ # Model of the robot which ISAR is connected to
321
+ # This should be set in the robot package settings.env file
322
+ ROBOT_MODEL: RobotModel = Field(default=RobotModel.Robot) # type: ignore
323
+
283
324
  class Config:
284
325
  env_file_encoding = "utf-8"
285
326
  case_sensitive = True
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Dict, List, Optional, Union
2
+ from typing import List, Optional, Union
3
3
 
4
4
  from alitra import Frame, Pose, Position
5
5
  from azure.identity import DefaultAzureCredential
@@ -6,7 +6,7 @@ from isar.models.communication.queues.status_queue import StatusQueue
6
6
 
7
7
 
8
8
  class Queues:
9
- def __init__(self):
9
+ def __init__(self) -> None:
10
10
  self.start_mission: QueueIO = QueueIO(input_size=1, output_size=1)
11
11
  self.stop_mission: QueueIO = QueueIO(input_size=1, output_size=1)
12
12
  self.pause_mission: QueueIO = QueueIO(input_size=1, output_size=1)
@@ -3,19 +3,19 @@ from enum import Enum
3
3
 
4
4
  class MissionStatus(str, Enum):
5
5
  NotStarted: str = "not_started"
6
- Started: str = "started"
7
6
  InProgress: str = "in_progress"
7
+ Paused: str = "paused"
8
8
  Failed: str = "failed"
9
9
  Cancelled: str = "cancelled"
10
- Completed: str = "completed"
11
- Paused: str = "paused"
10
+ Successful: str = "successful"
11
+ PartiallySuccessful: str = "partially_successful"
12
12
 
13
13
 
14
14
  class TaskStatus(str, Enum):
15
15
  NotStarted: str = "not_started"
16
16
  InProgress: str = "in_progress"
17
- PartiallySuccessful: str = "partially_successful"
17
+ Paused: str = "paused"
18
18
  Failed: str = "failed"
19
19
  Cancelled: str = "cancelled"
20
20
  Successful: str = "successful"
21
- Paused: str = "paused"
21
+ PartiallySuccessful: str = "partially_successful"
@@ -17,8 +17,10 @@ class MissionMetadata:
17
17
  source_url: Optional[str] = None
18
18
  plant_code: str = settings.PLANT_CODE
19
19
  plant_name: str = settings.PLANT_NAME
20
+ plant_short_name: str = settings.PLANT_SHORT_NAME
20
21
  media_orientation_reference_system: str = (
21
22
  settings.MEDIA_ORIENTATION_REFERENCE_SYSTEM
22
23
  )
23
24
  robot_id: str = settings.ROBOT_ID
25
+ robot_model: str = settings.ROBOT_TYPE
24
26
  mission_date: date = datetime.utcnow().date()
isar/modules.py CHANGED
@@ -36,8 +36,9 @@ class APIModule(Module):
36
36
  self,
37
37
  authenticator: Authenticator,
38
38
  scheduling_controller: SchedulingController,
39
+ keyvault: Keyvault,
39
40
  ) -> API:
40
- return API(authenticator, scheduling_controller)
41
+ return API(authenticator, scheduling_controller, keyvault)
41
42
 
42
43
  @provider
43
44
  @singleton
@@ -153,7 +154,7 @@ class ServiceModule(Module):
153
154
  @provider
154
155
  @singleton
155
156
  def provide_keyvault(self) -> Keyvault:
156
- return Keyvault(keyvault_name=settings.KEYVAULT)
157
+ return Keyvault(keyvault_name=settings.KEYVAULT_NAME)
157
158
 
158
159
  @provider
159
160
  @singleton
@@ -1,7 +1,5 @@
1
- import json
2
1
  import logging
3
2
  import os
4
- from datetime import datetime
5
3
  from queue import Empty, Queue
6
4
 
7
5
  import backoff
@@ -10,7 +8,6 @@ from paho.mqtt.client import Client
10
8
 
11
9
  from isar.config.settings import settings
12
10
  from robot_interface.telemetry.mqtt_client import MqttClientInterface
13
- from robot_interface.utilities.json_service import EnhancedJSONEncoder
14
11
 
15
12
 
16
13
  def _on_success(data: dict) -> None:
@@ -74,21 +71,6 @@ class MqttClient(MqttClientInterface):
74
71
  def run(self) -> None:
75
72
  self.connect(host=self.host, port=self.port)
76
73
  self.client.loop_start()
77
- payload: str = json.dumps(
78
- {
79
- "robot_id": settings.ROBOT_ID,
80
- "host": settings.API_HOST_VIEWED_EXTERNALLY,
81
- "port": settings.API_PORT,
82
- "timestamp": datetime.utcnow(),
83
- },
84
- cls=EnhancedJSONEncoder,
85
- )
86
-
87
- self.publish(
88
- topic=settings.TOPIC_ISAR_ROBOT,
89
- payload=payload,
90
- retain=True,
91
- )
92
74
 
93
75
  while True:
94
76
  if not self.client.is_connected():
@@ -0,0 +1,33 @@
1
+ import json
2
+ import time
3
+ from datetime import datetime
4
+ from queue import Queue
5
+
6
+ from isar.config.settings import robot_settings, settings
7
+ from robot_interface.telemetry.mqtt_client import MqttPublisher
8
+ from robot_interface.telemetry.payloads import RobotInfoPayload
9
+ from robot_interface.utilities.json_service import EnhancedJSONEncoder
10
+
11
+
12
+ class RobotInfoPublisher:
13
+ def __init__(self, mqtt_queue: Queue):
14
+ self.mqtt_publisher: MqttPublisher = MqttPublisher(mqtt_queue=mqtt_queue)
15
+
16
+ def run(self) -> None:
17
+ while True:
18
+ payload: RobotInfoPayload = RobotInfoPayload(
19
+ robot_name=settings.ROBOT_ID,
20
+ robot_model=robot_settings.ROBOT_MODEL, # type: ignore
21
+ robot_serial_number=settings.SERIAL_NUMBER,
22
+ video_streams=settings.VIDEO_STREAMS,
23
+ host=settings.API_HOST_VIEWED_EXTERNALLY,
24
+ port=settings.API_PORT,
25
+ timestamp=datetime.utcnow(),
26
+ )
27
+
28
+ self.mqtt_publisher.publish(
29
+ topic=settings.TOPIC_ISAR_ROBOT_INFO,
30
+ payload=json.dumps(payload, cls=EnhancedJSONEncoder),
31
+ )
32
+
33
+ time.sleep(settings.ROBOT_INFO_PUBLISH_INTERVAL)
@@ -0,0 +1,66 @@
1
+ import json
2
+ import time
3
+ from datetime import datetime
4
+ from queue import Queue
5
+ from threading import Thread
6
+
7
+ from isar.config.settings import settings
8
+ from isar.state_machine.state_machine import StateMachine
9
+ from robot_interface.models.mission.status import RobotStatus
10
+ from robot_interface.robot_interface import RobotInterface
11
+ from robot_interface.telemetry.mqtt_client import MqttPublisher
12
+ from robot_interface.telemetry.payloads import RobotStatusPayload
13
+ from robot_interface.utilities.json_service import EnhancedJSONEncoder
14
+
15
+
16
+ class RobotStatusPublisher:
17
+ def __init__(
18
+ self, mqtt_queue: Queue, robot: RobotInterface, state_machine: StateMachine
19
+ ):
20
+ self.mqtt_publisher: MqttPublisher = MqttPublisher(mqtt_queue=mqtt_queue)
21
+ self.robot: RobotInterface = robot
22
+ self.state_machine: StateMachine = state_machine
23
+
24
+ def run(self) -> None:
25
+ robot_status_monitor: RobotStatusMonitor = RobotStatusMonitor(robot=self.robot)
26
+ robot_status_thread: Thread = Thread(
27
+ target=robot_status_monitor.run,
28
+ name="Robot Status Monitor",
29
+ daemon=True,
30
+ )
31
+ robot_status_thread.start()
32
+
33
+ while True:
34
+ payload: RobotStatusPayload = RobotStatusPayload(
35
+ robot_name=settings.ROBOT_ID,
36
+ robot_status=robot_status_monitor.robot_status,
37
+ current_isar_state=self.state_machine.current_state,
38
+ current_mission_id=self.state_machine.current_mission.id
39
+ if self.state_machine.current_mission
40
+ else None,
41
+ current_task_id=self.state_machine.current_task.id
42
+ if self.state_machine.current_task
43
+ else None,
44
+ current_step_id=self.state_machine.current_step.id
45
+ if self.state_machine.current_step
46
+ else None,
47
+ timestamp=datetime.utcnow(),
48
+ )
49
+
50
+ self.mqtt_publisher.publish(
51
+ topic=settings.TOPIC_ISAR_ROBOT_STATUS,
52
+ payload=json.dumps(payload, cls=EnhancedJSONEncoder),
53
+ )
54
+
55
+ time.sleep(settings.ROBOT_STATUS_PUBLISH_INTERVAL)
56
+
57
+
58
+ class RobotStatusMonitor:
59
+ def __init__(self, robot: RobotInterface):
60
+ self.robot = robot
61
+ self.robot_status: RobotStatus = RobotStatus.Offline
62
+
63
+ def run(self) -> None:
64
+ while True:
65
+ self.robot_status = self.robot.robot_status()
66
+ time.sleep(settings.ROBOT_API_STATUS_POLL_INTERVAL)
@@ -51,7 +51,7 @@ class RequestHandler:
51
51
  response.raise_for_status()
52
52
  except HTTPError:
53
53
  self.logger.exception(
54
- f"Http error. Http status code= {response.status_code}"
54
+ f"Http error. Http status code= {response.status_code}, Content: {response.content}"
55
55
  )
56
56
  raise
57
57
  return response
@@ -67,7 +67,7 @@ class SchedulingUtilities:
67
67
  ------
68
68
  HTTPException 404 Not Found
69
69
  If requested mission with mission_id is not found
70
- HTTPException 500 Internal server error
70
+ HTTPException 500 Internal Server Error
71
71
  If for some reason the mission can not be returned
72
72
  """
73
73
  try:
@@ -151,7 +151,7 @@ class SchedulingUtilities:
151
151
  self.queues.start_mission,
152
152
  )
153
153
  except QueueTimeoutError:
154
- error_message = "Internal server error - Failed to start mission in ISAR"
154
+ error_message = "Internal Server Error - Failed to start mission in ISAR"
155
155
  self.logger.error(error_message)
156
156
  raise HTTPException(
157
157
  status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
@@ -169,7 +169,7 @@ class SchedulingUtilities:
169
169
  try:
170
170
  return self._send_command(True, self.queues.pause_mission)
171
171
  except QueueTimeoutError:
172
- error_message = "Internal server error - Failed to pause mission"
172
+ error_message = "Internal Server Error - Failed to pause mission"
173
173
  self.logger.error(error_message)
174
174
  raise HTTPException(
175
175
  status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
@@ -188,7 +188,7 @@ class SchedulingUtilities:
188
188
  try:
189
189
  return self._send_command(True, self.queues.resume_mission)
190
190
  except QueueTimeoutError:
191
- error_message = "Internal server error - Failed to resume mission"
191
+ error_message = "Internal Server Error - Failed to resume mission"
192
192
  self.logger.error(error_message)
193
193
  raise HTTPException(
194
194
  status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
@@ -209,7 +209,7 @@ class SchedulingUtilities:
209
209
  True, self.queues.stop_mission
210
210
  )
211
211
  except QueueTimeoutError:
212
- error_message = "Internal server error - Failed to stop mission"
212
+ error_message = "Internal Server Error - Failed to stop mission"
213
213
  self.logger.error(error_message)
214
214
  raise HTTPException(
215
215
  status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
@@ -3,7 +3,7 @@ from typing import Any, Optional
3
3
 
4
4
 
5
5
  class ThreadedRequest:
6
- def __init__(self, request_func: Any) -> None:
6
+ def __init__(self, request_func: Any):
7
7
  self._thread: Optional[Thread] = None
8
8
  self._request_func: Any = request_func
9
9
  self._output: Optional[Any] = None
@@ -11,11 +11,11 @@ class ThreadedRequest:
11
11
  self._exception: Optional[Exception] = None
12
12
  self._exception_lock: Lock = Lock()
13
13
 
14
- def start_thread(self, *request_args) -> bool:
14
+ def start_thread(self, *request_args, **kwargs) -> bool:
15
15
  if self._is_thread_alive():
16
16
  return False
17
17
  self._output = None
18
- self._thread = Thread(target=self._thread_func, args=request_args)
18
+ self._thread = Thread(target=self._thread_func, args=request_args, **kwargs)
19
19
  self._thread.start()
20
20
  return True
21
21
 
@@ -7,7 +7,7 @@ from datetime import datetime
7
7
  from typing import Deque, List, Optional
8
8
 
9
9
  from alitra import Pose
10
- from injector import Injector, inject
10
+ from injector import inject
11
11
  from transitions import Machine
12
12
  from transitions.core import State
13
13
 
@@ -262,7 +262,15 @@ class StateMachine(object):
262
262
  self.update_current_step()
263
263
 
264
264
  def _mission_finished(self) -> None:
265
- self.current_mission.status = MissionStatus.Completed
265
+ fail_statuses: List[TaskStatus] = [
266
+ TaskStatus.Cancelled,
267
+ TaskStatus.Failed,
268
+ TaskStatus.PartiallySuccessful,
269
+ ]
270
+ if any(task.status in fail_statuses for task in self.current_mission.tasks):
271
+ self.current_mission.status = MissionStatus.PartiallySuccessful
272
+ else:
273
+ self.current_mission.status = MissionStatus.Successful
266
274
  self._finalize()
267
275
 
268
276
  def _mission_started(self) -> None:
@@ -375,6 +383,7 @@ class StateMachine(object):
375
383
  self.current_step = None
376
384
  self.current_task = None
377
385
  self.current_mission = None
386
+ self.initial_pose = None
378
387
 
379
388
  def start_mission(self, mission: Mission, initial_pose: Pose):
380
389
  """Starts a scheduled mission."""
@@ -490,7 +499,7 @@ class StateMachine(object):
490
499
  self.mqtt_publisher.publish(
491
500
  topic=settings.TOPIC_ISAR_STATE,
492
501
  payload=payload,
493
- retain=True,
502
+ retain=False,
494
503
  )
495
504
 
496
505
  def _log_state_transition(self, next_state):
@@ -524,7 +533,6 @@ class StateMachine(object):
524
533
  )
525
534
 
526
535
 
527
- def main(injector: Injector):
536
+ def main(state_machine: StateMachine):
528
537
  """Starts a state machine instance."""
529
- state_machine = injector.get(StateMachine)
530
538
  state_machine.begin()