isar 1.20.2__py3-none-any.whl → 1.34.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. isar/apis/api.py +148 -76
  2. isar/apis/models/__init__.py +0 -1
  3. isar/apis/models/models.py +21 -11
  4. isar/apis/models/start_mission_definition.py +110 -168
  5. isar/apis/robot_control/robot_controller.py +41 -0
  6. isar/apis/schedule/scheduling_controller.py +124 -162
  7. isar/apis/security/authentication.py +5 -5
  8. isar/config/certs/ca-cert.pem +33 -31
  9. isar/config/keyvault/keyvault_service.py +1 -1
  10. isar/config/log.py +45 -40
  11. isar/config/logging.conf +16 -31
  12. isar/config/open_telemetry.py +102 -0
  13. isar/config/predefined_mission_definition/default_exr.json +0 -2
  14. isar/config/predefined_mission_definition/default_mission.json +1 -5
  15. isar/config/predefined_mission_definition/default_turtlebot.json +4 -11
  16. isar/config/predefined_missions/default.json +67 -87
  17. isar/config/predefined_missions/default_extra_capabilities.json +107 -0
  18. isar/config/settings.py +76 -111
  19. isar/eventhandlers/eventhandler.py +123 -0
  20. isar/mission_planner/local_planner.py +6 -20
  21. isar/mission_planner/mission_planner_interface.py +1 -1
  22. isar/models/events.py +184 -0
  23. isar/models/status.py +18 -0
  24. isar/modules.py +118 -199
  25. isar/robot/robot.py +377 -0
  26. isar/robot/robot_battery.py +60 -0
  27. isar/robot/robot_monitor_mission.py +357 -0
  28. isar/robot/robot_pause_mission.py +74 -0
  29. isar/robot/robot_resume_mission.py +67 -0
  30. isar/robot/robot_start_mission.py +66 -0
  31. isar/robot/robot_status.py +61 -0
  32. isar/robot/robot_stop_mission.py +68 -0
  33. isar/robot/robot_upload_inspection.py +75 -0
  34. isar/script.py +57 -40
  35. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  36. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
  37. isar/services/service_connections/mqtt/robot_info_publisher.py +3 -3
  38. isar/services/service_connections/persistent_memory.py +69 -0
  39. isar/services/utilities/mqtt_utilities.py +93 -0
  40. isar/services/utilities/robot_utilities.py +20 -0
  41. isar/services/utilities/scheduling_utilities.py +393 -65
  42. isar/state_machine/state_machine.py +219 -538
  43. isar/state_machine/states/__init__.py +0 -8
  44. isar/state_machine/states/await_next_mission.py +114 -0
  45. isar/state_machine/states/blocked_protective_stop.py +60 -0
  46. isar/state_machine/states/going_to_lockdown.py +95 -0
  47. isar/state_machine/states/going_to_recharging.py +92 -0
  48. isar/state_machine/states/home.py +115 -0
  49. isar/state_machine/states/intervention_needed.py +77 -0
  50. isar/state_machine/states/lockdown.py +38 -0
  51. isar/state_machine/states/maintenance.py +36 -0
  52. isar/state_machine/states/monitor.py +137 -247
  53. isar/state_machine/states/offline.py +51 -53
  54. isar/state_machine/states/paused.py +92 -23
  55. isar/state_machine/states/pausing.py +48 -0
  56. isar/state_machine/states/pausing_return_home.py +48 -0
  57. isar/state_machine/states/recharging.py +80 -0
  58. isar/state_machine/states/resuming.py +57 -0
  59. isar/state_machine/states/resuming_return_home.py +64 -0
  60. isar/state_machine/states/return_home_paused.py +109 -0
  61. isar/state_machine/states/returning_home.py +217 -0
  62. isar/state_machine/states/stopping.py +61 -0
  63. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  64. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  65. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  66. isar/state_machine/states/stopping_return_home.py +77 -0
  67. isar/state_machine/states/unknown_status.py +72 -0
  68. isar/state_machine/states_enum.py +21 -5
  69. isar/state_machine/transitions/mission.py +192 -0
  70. isar/state_machine/transitions/return_home.py +106 -0
  71. isar/state_machine/transitions/robot_status.py +80 -0
  72. isar/state_machine/utils/common_event_handlers.py +73 -0
  73. isar/storage/blob_storage.py +70 -52
  74. isar/storage/local_storage.py +25 -12
  75. isar/storage/storage_interface.py +28 -7
  76. isar/storage/uploader.py +174 -55
  77. isar/storage/utilities.py +32 -29
  78. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/METADATA +73 -110
  79. isar-1.34.9.dist-info/RECORD +135 -0
  80. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
  81. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/entry_points.txt +1 -0
  82. robot_interface/models/exceptions/robot_exceptions.py +91 -41
  83. robot_interface/models/initialize/__init__.py +0 -1
  84. robot_interface/models/inspection/__init__.py +0 -13
  85. robot_interface/models/inspection/inspection.py +42 -33
  86. robot_interface/models/mission/mission.py +14 -15
  87. robot_interface/models/mission/status.py +20 -26
  88. robot_interface/models/mission/task.py +154 -121
  89. robot_interface/models/robots/battery_state.py +6 -0
  90. robot_interface/models/robots/media.py +13 -0
  91. robot_interface/models/robots/robot_model.py +7 -7
  92. robot_interface/robot_interface.py +119 -84
  93. robot_interface/telemetry/mqtt_client.py +74 -12
  94. robot_interface/telemetry/payloads.py +91 -13
  95. robot_interface/utilities/json_service.py +7 -1
  96. isar/config/predefined_missions/default_turtlebot.json +0 -110
  97. isar/config/predefined_poses/__init__.py +0 -0
  98. isar/config/predefined_poses/predefined_poses.py +0 -616
  99. isar/config/settings.env +0 -25
  100. isar/mission_planner/sequential_task_selector.py +0 -23
  101. isar/mission_planner/task_selector_interface.py +0 -31
  102. isar/models/communication/__init__.py +0 -0
  103. isar/models/communication/message.py +0 -12
  104. isar/models/communication/queues/__init__.py +0 -4
  105. isar/models/communication/queues/queue_io.py +0 -12
  106. isar/models/communication/queues/queue_timeout_error.py +0 -2
  107. isar/models/communication/queues/queues.py +0 -19
  108. isar/models/communication/queues/status_queue.py +0 -20
  109. isar/models/mission_metadata/__init__.py +0 -0
  110. isar/services/readers/__init__.py +0 -0
  111. isar/services/readers/base_reader.py +0 -37
  112. isar/services/service_connections/stid/__init__.py +0 -0
  113. isar/services/utilities/queue_utilities.py +0 -39
  114. isar/state_machine/states/idle.py +0 -85
  115. isar/state_machine/states/initialize.py +0 -71
  116. isar/state_machine/states/initiate.py +0 -142
  117. isar/state_machine/states/off.py +0 -18
  118. isar/state_machine/states/stop.py +0 -95
  119. isar/storage/slimm_storage.py +0 -191
  120. isar-1.20.2.dist-info/RECORD +0 -116
  121. robot_interface/models/initialize/initialize_params.py +0 -9
  122. robot_interface/models/mission/step.py +0 -234
  123. {isar-1.20.2.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
  124. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
@@ -1,37 +1,30 @@
1
1
  import logging
2
2
  from http import HTTPStatus
3
- from typing import List, Optional
4
3
 
5
- from alitra import Pose
6
4
  from fastapi import Body, HTTPException, Path
7
- from injector import inject
5
+ from opentelemetry import trace
8
6
 
9
- from isar.apis.models import InputPose, StartMissionResponse
10
7
  from isar.apis.models.models import (
11
8
  ControlMissionResponse,
12
- RobotInfoResponse,
9
+ StartMissionResponse,
13
10
  TaskResponse,
14
11
  )
15
12
  from isar.apis.models.start_mission_definition import (
16
13
  StartMissionDefinition,
14
+ StopMissionDefinition,
17
15
  to_isar_mission,
18
16
  )
19
- from isar.config.settings import robot_settings, settings
17
+ from isar.config.settings import robot_settings
20
18
  from isar.mission_planner.mission_planner_interface import MissionPlannerError
21
19
  from isar.services.utilities.scheduling_utilities import SchedulingUtilities
22
20
  from isar.state_machine.states_enum import States
23
21
  from robot_interface.models.mission.mission import Mission
24
- from robot_interface.models.mission.step import (
25
- DriveToPose,
26
- Localize,
27
- MoveArm,
28
- ReturnToHome,
29
- )
30
- from robot_interface.models.mission.task import Task
22
+ from robot_interface.models.mission.task import TASKS, InspectionTask
23
+
24
+ tracer = trace.get_tracer(__name__)
31
25
 
32
26
 
33
27
  class SchedulingController:
34
- @inject
35
28
  def __init__(
36
29
  self,
37
30
  scheduling_utilities: SchedulingUtilities,
@@ -39,6 +32,7 @@ class SchedulingController:
39
32
  self.scheduling_utilities: SchedulingUtilities = scheduling_utilities
40
33
  self.logger = logging.getLogger("api")
41
34
 
35
+ @tracer.start_as_current_span("start_mission_by_id")
42
36
  def start_mission_by_id(
43
37
  self,
44
38
  mission_id: str = Path(
@@ -46,46 +40,25 @@ class SchedulingController:
46
40
  title="Mission ID",
47
41
  description="ID-number for predefined mission",
48
42
  ),
49
- initial_pose: Optional[InputPose] = Body(
50
- default=None,
51
- description="The starting point of the mission. Used for initial "
52
- "localization of robot",
53
- embed=True,
54
- ),
55
- return_pose: Optional[InputPose] = Body(
56
- default=None,
57
- description="End pose of the mission. The robot return to the specified "
58
- "pose after finishing all inspections",
59
- embed=True,
60
- ),
61
43
  ) -> StartMissionResponse:
62
- self.logger.info(f"Received request to start mission with id {mission_id}")
44
+ self.logger.info("Received request to start mission with id %s", mission_id)
63
45
 
64
46
  state: States = self.scheduling_utilities.get_state()
65
47
  self.scheduling_utilities.verify_state_machine_ready_to_receive_mission(state)
66
48
 
67
49
  mission: Mission = self.scheduling_utilities.get_mission(mission_id)
68
- if return_pose:
69
- pose: Pose = return_pose.to_alitra_pose()
70
- step: DriveToPose = DriveToPose(pose=pose)
71
- mission.tasks.append(Task(steps=[step]))
72
50
 
73
51
  self.scheduling_utilities.verify_robot_capable_of_mission(
74
52
  mission=mission, robot_capabilities=robot_settings.CAPABILITIES
75
53
  )
76
54
 
77
- initial_pose_alitra: Optional[Pose] = (
78
- initial_pose.to_alitra_pose() if initial_pose else None
79
- )
55
+ self.logger.info("Starting mission with ISAR Mission ID: '%s'", mission.id)
80
56
 
81
- self.logger.info(f"Starting mission with ISAR Mission ID: '{mission.id}'")
82
-
83
- self.scheduling_utilities.start_mission(
84
- mission=mission, initial_pose=initial_pose_alitra
85
- )
57
+ self.scheduling_utilities.start_mission(mission=mission)
86
58
 
87
59
  return self._api_response(mission)
88
60
 
61
+ @tracer.start_as_current_span("start_mission")
89
62
  def start_mission(
90
63
  self,
91
64
  mission_definition: StartMissionDefinition = Body(
@@ -94,35 +67,26 @@ class SchedulingController:
94
67
  title="Mission Definition",
95
68
  description="Description of the mission in json format",
96
69
  ),
97
- initial_pose: Optional[InputPose] = Body(
98
- default=None,
99
- description="The starting point of the mission. Used for initial "
100
- "localization of robot",
101
- embed=True,
102
- ),
103
- return_pose: Optional[InputPose] = Body(
104
- default=None,
105
- description="End pose of the mission. The robot return to the specified "
106
- "pose after finishing all inspections",
107
- embed=True,
108
- ),
109
70
  ) -> StartMissionResponse:
110
71
  self.logger.info("Received request to start new mission")
111
72
 
112
73
  if not mission_definition:
113
- error_message: str = (
74
+ error_message_no_mission_definition: str = (
114
75
  "Unprocessable entity - 'mission_definition' empty or invalid"
115
76
  )
116
- self.logger.error(error_message)
77
+ self.logger.error(error_message_no_mission_definition)
117
78
  raise HTTPException(
118
- status_code=HTTPStatus.UNPROCESSABLE_ENTITY, detail=error_message
79
+ status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
80
+ detail=error_message_no_mission_definition,
119
81
  )
120
82
 
121
83
  state: States = self.scheduling_utilities.get_state()
122
84
  self.scheduling_utilities.verify_state_machine_ready_to_receive_mission(state)
123
85
 
124
86
  try:
125
- mission: Mission = to_isar_mission(mission_definition)
87
+ mission: Mission = to_isar_mission(
88
+ start_mission_definition=mission_definition
89
+ )
126
90
  except MissionPlannerError as e:
127
91
  error_message = f"Bad Request - Cannot create ISAR mission: {e}"
128
92
  self.logger.warning(error_message)
@@ -134,21 +98,23 @@ class SchedulingController:
134
98
  self.scheduling_utilities.verify_robot_capable_of_mission(
135
99
  mission=mission, robot_capabilities=robot_settings.CAPABILITIES
136
100
  )
137
- if return_pose:
138
- pose: Pose = return_pose.to_alitra_pose()
139
- step: DriveToPose = DriveToPose(pose=pose)
140
- mission.tasks.append(Task(steps=[step]))
141
101
 
142
- initial_pose_alitra: Optional[Pose] = (
143
- initial_pose.to_alitra_pose() if initial_pose else None
144
- )
102
+ self.logger.info("Starting mission: %s", mission.id)
103
+ self.scheduling_utilities.start_mission(mission=mission)
104
+ return self._api_response(mission)
105
+
106
+ @tracer.start_as_current_span("return_home")
107
+ def return_home(self) -> None:
108
+ self.logger.info("Received request to return home")
145
109
 
146
- self.logger.info(f"Starting mission: {mission.id}")
147
- self.scheduling_utilities.start_mission(
148
- mission=mission, initial_pose=initial_pose_alitra
110
+ state: States = self.scheduling_utilities.get_state()
111
+ self.scheduling_utilities.verify_state_machine_ready_to_receive_return_home_mission(
112
+ state
149
113
  )
150
- return self._api_response(mission)
151
114
 
115
+ self.scheduling_utilities.return_home()
116
+
117
+ @tracer.start_as_current_span("pause_mission")
152
118
  def pause_mission(self) -> ControlMissionResponse:
153
119
  self.logger.info("Received request to pause current mission")
154
120
 
@@ -156,7 +122,7 @@ class SchedulingController:
156
122
 
157
123
  if state not in [
158
124
  States.Monitor,
159
- States.Initiate,
125
+ States.ReturningHome,
160
126
  ]:
161
127
  error_message = (
162
128
  f"Conflict - Pause command received in invalid state - State: {state}"
@@ -172,12 +138,13 @@ class SchedulingController:
172
138
  )
173
139
  return pause_mission_response
174
140
 
141
+ @tracer.start_as_current_span("resume_mission")
175
142
  def resume_mission(self) -> ControlMissionResponse:
176
143
  self.logger.info("Received request to resume current mission")
177
144
 
178
145
  state: States = self.scheduling_utilities.get_state()
179
146
 
180
- if state != States.Paused:
147
+ if state not in [States.Paused, States.ReturnHomePaused]:
181
148
  error_message = (
182
149
  f"Conflict - Resume command received in invalid state - State: {state}"
183
150
  )
@@ -189,12 +156,29 @@ class SchedulingController:
189
156
  )
190
157
  return resume_mission_response
191
158
 
192
- def stop_mission(self) -> ControlMissionResponse:
159
+ @tracer.start_as_current_span("stop_mission")
160
+ def stop_mission(
161
+ self,
162
+ mission_id: StopMissionDefinition = Body(
163
+ default=None,
164
+ embed=True,
165
+ title="Mission ID to stop",
166
+ description="The mission ID of the mission being stopped, in json format",
167
+ ),
168
+ ) -> ControlMissionResponse:
169
+
193
170
  self.logger.info("Received request to stop current mission")
194
171
 
195
172
  state: States = self.scheduling_utilities.get_state()
196
173
 
197
- if state in [States.Off, States.Idle]:
174
+ if (
175
+ state == States.UnknownStatus
176
+ or state == States.Stopping
177
+ or state == States.BlockedProtectiveStop
178
+ or state == States.Offline
179
+ or state == States.Home
180
+ or state == States.ReturningHome
181
+ ):
198
182
  error_message = (
199
183
  f"Conflict - Stop command received in invalid state - State: {state}"
200
184
  )
@@ -202,119 +186,94 @@ class SchedulingController:
202
186
  raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
203
187
 
204
188
  stop_mission_response: ControlMissionResponse = (
205
- self.scheduling_utilities.stop_mission()
189
+ self.scheduling_utilities.stop_mission(mission_id.mission_id)
206
190
  )
207
191
  return stop_mission_response
208
192
 
209
- def drive_to(
210
- self,
211
- target_pose: InputPose = Body(
212
- default=None,
213
- title="Target Pose",
214
- description="The target pose for the drive_to step",
215
- ),
216
- ) -> StartMissionResponse:
217
- self.logger.info("Received request to start new drive-to mission")
193
+ @tracer.start_as_current_span("release_intervention_needed")
194
+ def release_intervention_needed(self) -> None:
195
+ self.logger.info("Received request to release intervention needed state")
218
196
 
219
197
  state: States = self.scheduling_utilities.get_state()
220
198
 
221
- self.scheduling_utilities.verify_state_machine_ready_to_receive_mission(state)
222
-
223
- pose: Pose = target_pose.to_alitra_pose()
224
- step: DriveToPose = DriveToPose(pose=pose)
225
- mission: Mission = Mission(tasks=[Task(steps=[step])])
199
+ if state != States.InterventionNeeded:
200
+ error_message = f"Conflict - Release intervention needed command received in invalid state - State: {state}"
201
+ self.logger.warning(error_message)
202
+ raise HTTPException(
203
+ status_code=HTTPStatus.CONFLICT,
204
+ detail=error_message,
205
+ )
226
206
 
227
- self.logger.info(
228
- f"Starting drive to mission with ISAR Mission ID: '{mission.id}'"
229
- )
230
- self.scheduling_utilities.start_mission(mission=mission, initial_pose=None)
231
- return self._api_response(mission)
207
+ self.scheduling_utilities.release_intervention_needed()
208
+ self.logger.info("Released intervention needed state successfully")
232
209
 
233
- def start_localization_mission(
234
- self,
235
- localization_pose: InputPose = Body(
236
- default=None,
237
- embed=True,
238
- title="Localization Pose",
239
- description="The current position of the robot",
240
- ),
241
- ) -> StartMissionResponse:
242
- self.logger.info("Received request to start new localization mission")
210
+ @tracer.start_as_current_span("lockdown")
211
+ def lockdown(self) -> None:
212
+ self.logger.info("Received request to lockdown robot")
243
213
 
244
214
  state: States = self.scheduling_utilities.get_state()
245
215
 
246
- self.scheduling_utilities.verify_state_machine_ready_to_receive_mission(state)
216
+ if state == States.Lockdown:
217
+ error_message = "Conflict - Lockdown command received in lockdown state"
218
+ self.logger.warning(error_message)
219
+ raise HTTPException(
220
+ status_code=HTTPStatus.CONFLICT,
221
+ detail=error_message,
222
+ )
247
223
 
248
- pose: Pose = localization_pose.to_alitra_pose()
249
- step: Localize = Localize(localization_pose=pose)
250
- mission: Mission = Mission(tasks=[Task(steps=[step])])
224
+ self.scheduling_utilities.lock_down_robot()
225
+ self.logger.info("Lockdown started successfully")
251
226
 
252
- self.logger.info(
253
- f"Starting localization mission with ISAR Mission ID: '{mission.id}'"
254
- )
255
- self.scheduling_utilities.start_mission(
256
- mission=mission,
257
- initial_pose=None,
258
- )
259
- return self._api_response(mission)
227
+ @tracer.start_as_current_span("release_lockdown")
228
+ def release_lockdown(self) -> None:
229
+ self.logger.info("Received request to release robot lockdown")
260
230
 
261
- def start_move_arm_mission(
262
- self,
263
- arm_pose_literal: str = Path(
264
- ...,
265
- alias="arm_pose_literal",
266
- title="Arm pose literal",
267
- description="Arm pose as a literal",
268
- ),
269
- ) -> StartMissionResponse:
270
- self.logger.info("Received request to start new move arm mission")
231
+ state: States = self.scheduling_utilities.get_state()
271
232
 
272
- if not robot_settings.VALID_ARM_POSES:
273
- error_message: str = (
274
- f"Received a request to move the arm but the robot "
275
- f"{settings.ROBOT_NAME} does not support moving an arm"
276
- )
233
+ if state != States.Lockdown:
234
+ error_message = f"Conflict - Release lockdown command received in invalid state - State: {state}"
277
235
  self.logger.warning(error_message)
278
236
  raise HTTPException(
279
- status_code=HTTPStatus.BAD_REQUEST, detail=error_message
237
+ status_code=HTTPStatus.CONFLICT,
238
+ detail=error_message,
280
239
  )
281
240
 
282
- if arm_pose_literal not in robot_settings.VALID_ARM_POSES:
283
- error_message = (
284
- f"Received a request to move the arm but the arm pose "
285
- f"{arm_pose_literal} is not supported by the robot "
286
- f"{settings.ROBOT_NAME}"
287
- )
288
- self.logger.warning(error_message)
241
+ self.scheduling_utilities.release_robot_lockdown()
242
+ self.logger.info("Released lockdown successfully")
243
+
244
+ @tracer.start_as_current_span("maintenance_mode")
245
+ def set_maintenance_mode(self) -> None:
246
+ self.logger.info("Received request to set maintenance_mode")
247
+
248
+ state: States = self.scheduling_utilities.get_state()
249
+
250
+ if state == States.Maintenance or state == States.StoppingDueToMaintenance:
251
+ message = f"Conflict - Call to set maintenance mode was given while in state {state}."
252
+ self.logger.info(message)
289
253
  raise HTTPException(
290
- status_code=HTTPStatus.BAD_REQUEST, detail=error_message
254
+ status_code=HTTPStatus.CONFLICT,
255
+ detail=message,
291
256
  )
292
257
 
293
- state: States = self.scheduling_utilities.get_state()
258
+ self.scheduling_utilities.set_maintenance_mode()
259
+ self.logger.info("Maintenance mode has been set")
294
260
 
295
- self.scheduling_utilities.verify_state_machine_ready_to_receive_mission(state)
261
+ @tracer.start_as_current_span("release_maintenance_mode")
262
+ def release_maintenance_mode(self) -> None:
263
+ self.logger.info("Received request to release robot from maintenance mode")
296
264
 
297
- step: MoveArm = MoveArm(arm_pose=arm_pose_literal)
298
- mission: Mission = Mission(tasks=[Task(steps=[step])])
265
+ state: States = self.scheduling_utilities.get_state()
299
266
 
300
- self.logger.info(
301
- f"Starting move arm mission with ISAR Mission ID: '{mission.id}'"
302
- )
303
- self.scheduling_utilities.start_mission(
304
- mission=mission,
305
- initial_pose=None,
306
- )
307
- return self._api_response(mission)
267
+ if state != States.Maintenance:
268
+ message = f"Conflict - Release maintenance mode command received in invalid state - State: {state}"
269
+ self.logger.info(message)
270
+ raise HTTPException(
271
+ status_code=HTTPStatus.CONFLICT,
272
+ detail=message,
273
+ )
308
274
 
309
- def get_info(self):
310
- return RobotInfoResponse(
311
- robot_package=settings.ROBOT_PACKAGE,
312
- isar_id=settings.ISAR_ID,
313
- robot_name=settings.ROBOT_NAME,
314
- robot_capabilities=robot_settings.CAPABILITIES,
315
- robot_map_name=settings.DEFAULT_MAP,
316
- plant_short_name=settings.PLANT_SHORT_NAME,
317
- )
275
+ self.scheduling_utilities.release_maintenance_mode()
276
+ self.logger.info("Maintenance mode successfully released")
318
277
 
319
278
  def _api_response(self, mission: Mission) -> StartMissionResponse:
320
279
  return StartMissionResponse(
@@ -322,9 +281,12 @@ class SchedulingController:
322
281
  tasks=[self._task_api_response(task) for task in mission.tasks],
323
282
  )
324
283
 
325
- def _task_api_response(self, task: Task) -> TaskResponse:
326
- steps: List[dict] = []
327
- for step in task.steps:
328
- steps.append({"id": step.id, "type": step.__class__.__name__})
284
+ def _task_api_response(self, task: TASKS) -> TaskResponse:
285
+ if isinstance(task, InspectionTask):
286
+ inspection_id = task.inspection_id
287
+ else:
288
+ inspection_id = None
329
289
 
330
- return TaskResponse(id=task.id, tag_id=task.tag_id, steps=steps)
290
+ return TaskResponse(
291
+ id=task.id, tag_id=task.tag_id, inspection_id=inspection_id, type=task.type
292
+ )
@@ -3,7 +3,7 @@ import logging
3
3
  from fastapi import Depends
4
4
  from fastapi.security.base import SecurityBase
5
5
  from fastapi_azure_auth import SingleTenantAzureAuthorizationCodeBearer
6
- from fastapi_azure_auth.exceptions import InvalidAuth
6
+ from fastapi_azure_auth.exceptions import InvalidAuthHttp
7
7
  from fastapi_azure_auth.user import User
8
8
  from pydantic import BaseModel
9
9
 
@@ -35,7 +35,7 @@ async def validate_has_role(user: User = Depends(azure_scheme)) -> None:
35
35
  Raises a 403 authorization error if not.
36
36
  """
37
37
  if settings.REQUIRED_ROLE not in user.roles:
38
- raise InvalidAuth(
38
+ raise InvalidAuthHttp(
39
39
  "Current user does not possess the required role for this endpoint"
40
40
  )
41
41
 
@@ -48,9 +48,9 @@ class Authenticator:
48
48
  self.logger = logging.getLogger("api")
49
49
  self.authentication_enabled: bool = authentication_enabled
50
50
  enabled_string = "enabled" if self.authentication_enabled else "disabled"
51
- self.logger.info(f"API authentication is {enabled_string}")
51
+ self.logger.info("API authentication is %s", 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
  """
@@ -1,33 +1,35 @@
1
1
  -----BEGIN CERTIFICATE-----
2
- MIIFrDCCA5QCCQDxWBtOiKrXyDANBgkqhkiG9w0BAQsFADCBlzELMAkGA1UEBhMC
3
- Tk8xDzANBgNVBAgMBkJlcmdlbjEPMA0GA1UEBwwGQmVyZ2VuMRQwEgYDVQQKDAtF
4
- cV9Sb2JvdGljczERMA8GA1UECwwIUm9ib3RpY3MxHDAaBgNVBAMME1JvYm90aWNz
5
- X1NlbGZTaWduZWQxHzAdBgkqhkiG9w0BCQEWEHRsbmVAZXF1aW5vci5jb20wHhcN
6
- MjMwOTI4MTIwMjU0WhcNMjQwOTI3MTIwMjU0WjCBlzELMAkGA1UEBhMCTk8xDzAN
7
- BgNVBAgMBkJlcmdlbjEPMA0GA1UEBwwGQmVyZ2VuMRQwEgYDVQQKDAtFcV9Sb2Jv
8
- dGljczERMA8GA1UECwwIUm9ib3RpY3MxHDAaBgNVBAMME1JvYm90aWNzX1NlbGZT
9
- aWduZWQxHzAdBgkqhkiG9w0BCQEWEHRsbmVAZXF1aW5vci5jb20wggIiMA0GCSqG
10
- SIb3DQEBAQUAA4ICDwAwggIKAoICAQDlId+Y8n3aQDx49siw+2iuJL+QPcr2W/Li
11
- XLKzKDaJ39oB7cIAfvX17c5tOgpTx+u/lsQBDfNhweclrPB4oCcJdDPFGWsTuI1H
12
- PE/6ytVpinBFub/RpIHY049LlGRPvDEIc+qWiwZBkwG38KO8Ly/4oU3vW28L0YF/
13
- LUDnsmZ/kZV/dBXe/z+1FqG7nqQC1kl0LmbQtIJT+yW9ybK/Olu1uMhGpy10cBca
14
- k+lZDZWhi1Y76jTqxlVR9bCsP0XWR9czbU26R29JzSAChm2gAy+ekAqO+ol0xbhG
15
- msVNn640zTPjIcsouHB/QjZEwJ/amGMS8o+wfQc+rOUQozRl1gEhFTby9Vn66IsE
16
- WRkXkI8AgGcxrnwOnVjgRaJPtgNWfL35EYEZxw5+mIKEhvEcApoTEcr7LUv8E10n
17
- afpZhEzjgBZGRLMw0myn21+PgsJwG/jDQHd/oDXyWRcDu53humJZN3b+WIZa2BXy
18
- 98H0ZNKyP78uvG3mrqG2kW3tgws4XOqRuBEEj5PJuddShtkhpUJtobwksTH5vDt7
19
- L5JiVKDEYvXT9xt9FW8cDC1ZO4RRbfbMLv9xs1CXrbS75Wz0TyAqUkAPFAwfFgR5
20
- etES5ZeI4oPFW+zxgTkqLvT2PZh6I39OHE+Gi8P7aKZfnb7QqT4GkckJ1JWXuPee
21
- urkiGIrvowIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQAAAjrgEWNnR2fEAMYtR/RK
22
- VLgycBph2b1l6I3kwfDHF+HNwlrAihvP8M8xjCmbTODHOZ9ZaHMqnAoODpZpxgdg
23
- J3lEo3NuzTqYBQ+hXO0PLhVl5jaolX6vD/i7zqzK4mnQ34zKQb8LaJlEeCwuVhyN
24
- Mjwc84JreVD+VOaXIydVZ4vVmfylv79JsM6j7e4YsNofpvEiCAk/R/apDGXnOCkD
25
- WwoTxRFGl9HMqYigHg5TTfszfC765njRxwsrgLWN5cFPNai28ueBG0473pdUGlgN
26
- tT8NtXClr7R3avJ+v1pM4MlJXbyvl7ADsl9Bj+iHQusZgKEq/rARk8yzh6vH8Pwa
27
- QrsbgQ0OLcMMQrDSsl8BrwzV7boO+KHM6ypBsxJWBIJPCTdTq5cGHMLL+5+vfmE+
28
- jXqdrrO7b+xiiFPszMBjmyGo8/55Pr63YYz7pyXKQtjyev91yO7+niCQ4+MkR5XQ
29
- NTQdcfdd/LZwLhO7BS6U08Ytcg0ZMr3tb/hxCHD8vx+XS6gSxXVHqDqMZlmEcLOo
30
- cOQy73tOFX4Htpge0vQKhnH0TIDPb8um2IivdWdMz8wAoNGwj+A/aP3Eh9qqVZ5w
31
- 1t/Erqpxw+FIiAjqRJ1yE9I7I3buhostxj0y4ry7g+faNtgu8hrrILz84kJw4YdC
32
- qtASXmP/Nm1dAHV2UjM33A==
2
+ MIIGGzCCBAOgAwIBAgIUCVuS8tL7R2bdjRJznkk1NN0oUa8wDQYJKoZIhvcNAQEL
3
+ BQAwgZwxCzAJBgNVBAYTAk5PMQ8wDQYDVQQIDAZCZXJnZW4xDzANBgNVBAcMBkJl
4
+ cmdlbjEUMBIGA1UECgwLRXFfUm9ib3RpY3MxETAPBgNVBAsMCFJvYm90aWNzMRww
5
+ GgYDVQQDDBNSb2JvdGljc19TZWxmU2lnbmVkMSQwIgYJKoZIhvcNAQkBFhVmZ19y
6
+ b2JvdHNAZXF1aW5vci5jb20wHhcNMjQxMjExMDczNzAzWhcNMjUxMjExMDczNzAz
7
+ WjCBnDELMAkGA1UEBhMCTk8xDzANBgNVBAgMBkJlcmdlbjEPMA0GA1UEBwwGQmVy
8
+ Z2VuMRQwEgYDVQQKDAtFcV9Sb2JvdGljczERMA8GA1UECwwIUm9ib3RpY3MxHDAa
9
+ BgNVBAMME1JvYm90aWNzX1NlbGZTaWduZWQxJDAiBgkqhkiG9w0BCQEWFWZnX3Jv
10
+ Ym90c0BlcXVpbm9yLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
11
+ ALVlT8EjA8UpXdARJM6YfhH+9DEyytb0SJYOEm9jWL7X0G5eMbNfKtfFj/BLQBBw
12
+ GDqyvhFa8SO56zM3Yi1Lir41JkpOKf2zJpNaL1syPxOA87MAm43UN/p2Mo6QAYpC
13
+ lJzmeNoagLFi/kIzGajeUwISOf//Zej0jK/9CpuvvNVHz4wRk4Ap9qtgrM9qhlNq
14
+ rZL4ZQODnvDZ/nwAYwEh9O2sbRr73aiHFcbxNAU2XxtT5UwbZMHom2Xic2Dmubqn
15
+ cWFU+C4upEqPvgYU/hxia/rkhHyicXmdXT6IzjVxPbrlPBHoKVodJOCQ3UoBQl5X
16
+ caHCluKOblA/yX+vEhv2h/m8XOtWiq0Jxnx+6D7B1piZfQnnNVSBT0bOYASb2cpn
17
+ 8rJE3RBdBwiqjKTotVTZ8PAS/GY0Gz3GoMAUCvIGA02+/cO9ENls9D79/86lgiCQ
18
+ UqyteNNmo+oMqKHBw9Jmeejw1kbpnJN1abc270+qCT17aeX8yiMza5CyUTQTtgFI
19
+ 62xIYaHWhN7GrxOvoMU8X0G1HINwjS5heIhu5bND/26dLZqV3aBUWwb4ql5QIce2
20
+ q5BQKmMqQLZF6J0aFL9dKJv5u3ioPFmiYPsPiRlwusPOoOFCiMt2kqZE3h3wxu6V
21
+ cXWdj0sH+dgxuGYTxrLuoSru4Xv/HPXh15mVY0/eI+u/AgMBAAGjUzBRMB0GA1Ud
22
+ DgQWBBQW2Ms6M3iadKVLOnraMlx6Exm8KjAfBgNVHSMEGDAWgBQW2Ms6M3iadKVL
23
+ OnraMlx6Exm8KjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQB0
24
+ XorfCLmVUN3aQWBouwG0fXHBqi3d4RA0vPsi8w+R/dOuHnLBLlHAiOkCMqW23bLs
25
+ ir2UVqpGXcp5keKGeCTj+bmdqhnS/B9eoaeA8np/zDPH834v+Qv7JA5ch50gYrL7
26
+ llF/02e5m0egP2xx9V7k90Du8OBm7xQgfbyrUcB2laC+loPyQiHMF24CU4ESfC4K
27
+ DBXOZm8Kd6+fvzEs8XDLKwjNb9MocHIJikysZTncxfhgVPaU5KCqF3afHkCkZGzn
28
+ Z+yMBfWbZ2WtM3lfwmM7z5NCTSqSFucW5+kw/OnzS3nQLyz2Q4nh4ApojsI3v+FH
29
+ xz8aCITPbVwKAHGYO9jbk9x8LlkzDPCCzBH7dGKLKaQSCoD+IOwig1cI7pM77WeI
30
+ BNC0g2ZCuLdb7c37/kT/1xGrIGUrpDsO34ZWW7E2X8kQPkAgqMNp0uXcnvIoNHO1
31
+ eVL08Om+7WbwK7wS3te5IWDq4rRWVW4Jv709w8z6porr3W8ipdtUoYqbgZI8jOiW
32
+ osY4UVcRwQ4S1oF5pNRzXlZAPwAnaDipglxHBiy9/ARS6HKpOCbmUHbj+goXYNyZ
33
+ ps8TcYc9LFOq4HBE4KKbV971jf/wQy1nVcm2qSbOJ9MPHM5BG22m1iVhSYbLPgE2
34
+ SBZvCS+voD8MqpBB1tPcvVqlPb1pFxf1lOc5QV3uOA==
33
35
  -----END CERTIFICATE-----
@@ -65,7 +65,7 @@ class Keyvault:
65
65
  raise KeyvaultError # type: ignore
66
66
 
67
67
  def get_secret_client(self) -> SecretClient:
68
- if self.client == None:
68
+ if self.client is None:
69
69
  try:
70
70
  credential: Union[ClientSecretCredential, DefaultAzureCredential]
71
71
  if self.client_id and self.client_secret and self.tenant_id:
isar/config/log.py CHANGED
@@ -1,66 +1,71 @@
1
1
  import logging
2
2
  import logging.config
3
+ import os
3
4
  from importlib.resources import as_file, files
4
5
 
5
6
  import yaml
6
- from opencensus.ext.azure.log_exporter import AzureLogHandler
7
7
  from uvicorn.logging import ColourizedFormatter
8
8
 
9
- from isar.config.configuration_error import ConfigurationError
10
- from isar.config.keyvault.keyvault_error import KeyvaultError
11
- from isar.config.keyvault.keyvault_service import Keyvault
12
9
  from isar.config.settings import settings
13
10
 
11
+ from .settings import Settings
14
12
 
15
- def setup_loggers(keyvault: Keyvault) -> None:
16
- log_levels: dict = settings.LOG_LEVELS
17
- source = files("isar").joinpath("config").joinpath("logging.conf")
18
- with as_file(source) as f:
19
- log_config = yaml.safe_load(open(f))
13
+
14
+ def setup_loggers() -> None:
15
+ log_config = load_log_config()
20
16
 
21
17
  logging.config.dictConfig(log_config)
22
18
 
19
+ env_log_levels = {}
20
+ for env_var, value in os.environ.items():
21
+ if env_var.endswith("_LOG_LEVEL"):
22
+ log_name = env_var.split("_LOG_LEVEL")[0].lower()
23
+ env_log_levels[log_name] = value.upper()
24
+
23
25
  handlers = []
24
26
  if settings.LOG_HANDLER_LOCAL_ENABLED:
25
- handlers.append(configure_console_handler(log_config=log_config))
26
- if settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED:
27
27
  handlers.append(
28
- configure_azure_handler(log_config=log_config, keyvault=keyvault)
28
+ configure_console_handler(log_config=log_config, settings=settings)
29
29
  )
30
30
 
31
31
  for log_handler in handlers:
32
- for loggers in log_config["loggers"].keys():
33
- logging.getLogger(loggers).addHandler(log_handler)
34
- logging.getLogger(loggers).setLevel(log_levels[loggers])
35
- logging.getLogger().addHandler(log_handler)
32
+ for logger_name, logger_config in log_config["loggers"].items():
33
+ logger = logging.getLogger(logger_name)
34
+ logger.addHandler(log_handler)
35
+ if "level" in logger_config:
36
+ logger.setLevel(logger_config["level"])
37
+ if logger_name in env_log_levels:
38
+ logger.setLevel(env_log_levels[logger_name])
39
+ root_logger = logging.getLogger()
40
+ root_logger.addHandler(log_handler)
41
+ if "level" in log_config.get("root", {}):
42
+ root_logger.setLevel(log_config["root"]["level"])
36
43
 
37
44
 
38
- def configure_console_handler(log_config: dict) -> logging.Handler:
45
+ def load_log_config():
46
+ source = files("isar").joinpath("config").joinpath("logging.conf")
47
+ with as_file(source) as f:
48
+ log_config = yaml.safe_load(open(f))
49
+ return log_config
50
+
51
+
52
+ def configure_console_handler(log_config: dict, settings: Settings) -> logging.Handler:
39
53
  handler = logging.StreamHandler()
40
54
  handler.setLevel(log_config["root"]["level"])
41
- handler.setFormatter(
42
- ColourizedFormatter(
43
- log_config["formatters"]["colourized"]["format"],
44
- style="{",
45
- use_colors=True,
55
+ if settings.DEBUG_LOG_FORMATTER:
56
+ handler.setFormatter(
57
+ ColourizedFormatter(
58
+ log_config["formatters"]["debug-formatter"]["format"],
59
+ style="{",
60
+ use_colors=True,
61
+ )
46
62
  )
47
- )
48
- return handler
49
-
50
-
51
- def configure_azure_handler(log_config: dict, keyvault: Keyvault) -> logging.Handler:
52
- connection_string: str
53
- try:
54
- connection_string = keyvault.get_secret(
55
- "application-insights-connection-string"
56
- ).value
57
- except KeyvaultError:
58
- message: str = (
59
- f"CRITICAL ERROR: Missing connection string for Application Insights in key vault '{keyvault.name}'."
63
+ else:
64
+ handler.setFormatter(
65
+ ColourizedFormatter(
66
+ log_config["formatters"]["colourized"]["format"],
67
+ style="{",
68
+ use_colors=True,
69
+ )
60
70
  )
61
- print(f"\n{message} \n")
62
- raise ConfigurationError(message)
63
-
64
- handler = AzureLogHandler(connection_string=connection_string)
65
- handler.setLevel(log_config["root"]["level"])
66
71
  return handler