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.
- isar/apis/models/start_mission_definition.py +55 -108
- isar/apis/robot_control/robot_controller.py +5 -4
- isar/apis/schedule/scheduling_controller.py +4 -32
- isar/apis/security/authentication.py +2 -2
- isar/config/settings.env +1 -3
- isar/config/settings.py +5 -5
- isar/models/communication/message.py +0 -4
- isar/models/communication/queues/queue_utils.py +27 -0
- isar/models/communication/queues/queues.py +23 -5
- isar/robot/robot.py +91 -0
- isar/robot/robot_start_mission.py +73 -0
- isar/robot/robot_status.py +46 -0
- isar/robot/robot_task_status.py +92 -0
- isar/script.py +7 -0
- isar/services/utilities/scheduling_utilities.py +15 -26
- isar/state_machine/state_machine.py +94 -183
- isar/state_machine/states/blocked_protective_stop.py +7 -19
- isar/state_machine/states/idle.py +12 -54
- isar/state_machine/states/monitor.py +43 -90
- isar/state_machine/states/offline.py +8 -33
- isar/state_machine/states/paused.py +1 -1
- isar/state_machine/states_enum.py +0 -2
- isar/state_machine/transitions/fail_mission.py +13 -0
- isar/state_machine/transitions/finish_mission.py +39 -0
- isar/state_machine/transitions/pause.py +24 -0
- isar/state_machine/transitions/resume.py +27 -0
- isar/state_machine/transitions/start_mission.py +73 -0
- isar/state_machine/transitions/stop.py +33 -0
- isar/state_machine/transitions/utils.py +10 -0
- isar/storage/slimm_storage.py +2 -2
- {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/METADATA +2 -3
- {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/RECORD +40 -30
- robot_interface/models/mission/task.py +1 -1
- robot_interface/telemetry/mqtt_client.py +0 -1
- robot_interface/telemetry/payloads.py +3 -3
- robot_interface/utilities/json_service.py +1 -1
- isar/state_machine/states/initialize.py +0 -70
- isar/state_machine/states/initiate.py +0 -111
- {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/LICENSE +0 -0
- {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/WHEEL +0 -0
- {isar-1.25.9.dist-info → isar-1.26.0.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
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 =
|
|
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=
|
|
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=
|
|
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="
|
|
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="
|
|
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="
|
|
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.
|
|
11
|
-
self.
|
|
12
|
-
self.
|
|
13
|
-
self.
|
|
14
|
-
|
|
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)
|