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.
- isar/apis/api.py +54 -4
- isar/apis/models/start_mission_definition.py +44 -35
- isar/apis/security/authentication.py +4 -5
- isar/config/keyvault/keyvault_service.py +38 -21
- isar/config/log.py +42 -13
- isar/config/predefined_mission_definition/__init__.py +0 -0
- isar/config/predefined_mission_definition/default_mission.json +98 -0
- isar/config/predefined_mission_definition/default_turtlebot.json +136 -0
- isar/config/predefined_missions/default.json +84 -84
- isar/config/settings.env +5 -0
- isar/config/settings.py +51 -10
- isar/mission_planner/echo_planner.py +1 -1
- isar/models/communication/queues/queues.py +1 -1
- isar/models/mission/status.py +5 -5
- isar/models/mission_metadata/mission_metadata.py +2 -0
- isar/modules.py +3 -2
- isar/services/service_connections/mqtt/mqtt_client.py +0 -18
- isar/services/service_connections/mqtt/robot_info_publisher.py +33 -0
- isar/services/service_connections/mqtt/robot_status_publisher.py +66 -0
- isar/services/service_connections/request_handler.py +1 -1
- isar/services/utilities/scheduling_utilities.py +5 -5
- isar/services/utilities/threaded_request.py +3 -3
- isar/state_machine/state_machine.py +13 -5
- isar/state_machine/states/idle.py +6 -6
- isar/state_machine/states/initialize.py +9 -8
- isar/state_machine/states/initiate_step.py +16 -16
- isar/state_machine/states/monitor.py +17 -11
- isar/state_machine/states/paused.py +6 -6
- isar/state_machine/states/stop_step.py +10 -10
- isar/state_machine/states_enum.py +0 -1
- isar/storage/local_storage.py +2 -2
- isar/storage/slimm_storage.py +107 -41
- isar/storage/uploader.py +4 -5
- isar/storage/utilities.py +1 -23
- {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/LICENSE +0 -0
- {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/METADATA +4 -1
- {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/RECORD +47 -40
- {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/WHEEL +0 -0
- {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/top_level.txt +0 -0
- robot_interface/models/inspection/inspection.py +3 -22
- robot_interface/models/mission/status.py +6 -1
- robot_interface/models/mission/step.py +5 -32
- robot_interface/models/robots/__init__.py +0 -0
- robot_interface/models/robots/robot_model.py +13 -0
- robot_interface/robot_interface.py +17 -0
- robot_interface/telemetry/payloads.py +34 -0
- robot_interface/utilities/json_service.py +3 -0
|
@@ -1,92 +1,92 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
{
|
|
5
|
-
"steps": [
|
|
2
|
+
"id": 1,
|
|
3
|
+
"tasks": [
|
|
6
4
|
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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="
|
|
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://
|
|
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
|
|
@@ -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)
|
isar/models/mission/status.py
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
17
|
+
Paused: str = "paused"
|
|
18
18
|
Failed: str = "failed"
|
|
19
19
|
Cancelled: str = "cancelled"
|
|
20
20
|
Successful: str = "successful"
|
|
21
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
-
|
|
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=
|
|
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(
|
|
536
|
+
def main(state_machine: StateMachine):
|
|
528
537
|
"""Starts a state machine instance."""
|
|
529
|
-
state_machine = injector.get(StateMachine)
|
|
530
538
|
state_machine.begin()
|