isar 1.15.0__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 (129) hide show
  1. isar/__init__.py +2 -5
  2. isar/apis/api.py +159 -66
  3. isar/apis/models/__init__.py +0 -1
  4. isar/apis/models/models.py +22 -12
  5. isar/apis/models/start_mission_definition.py +128 -123
  6. isar/apis/robot_control/robot_controller.py +41 -0
  7. isar/apis/schedule/scheduling_controller.py +135 -121
  8. isar/apis/security/authentication.py +5 -5
  9. isar/config/certs/ca-cert.pem +32 -32
  10. isar/config/keyvault/keyvault_service.py +1 -2
  11. isar/config/log.py +47 -39
  12. isar/config/logging.conf +16 -31
  13. isar/config/open_telemetry.py +102 -0
  14. isar/config/predefined_mission_definition/default_exr.json +49 -0
  15. isar/config/predefined_mission_definition/default_mission.json +1 -5
  16. isar/config/predefined_mission_definition/default_turtlebot.json +4 -11
  17. isar/config/predefined_missions/default.json +67 -87
  18. isar/config/predefined_missions/default_extra_capabilities.json +107 -0
  19. isar/config/settings.py +119 -142
  20. isar/eventhandlers/eventhandler.py +123 -0
  21. isar/mission_planner/local_planner.py +6 -20
  22. isar/mission_planner/mission_planner_interface.py +1 -1
  23. isar/models/events.py +184 -0
  24. isar/models/status.py +18 -0
  25. isar/modules.py +118 -205
  26. isar/robot/robot.py +377 -0
  27. isar/robot/robot_battery.py +60 -0
  28. isar/robot/robot_monitor_mission.py +357 -0
  29. isar/robot/robot_pause_mission.py +74 -0
  30. isar/robot/robot_resume_mission.py +67 -0
  31. isar/robot/robot_start_mission.py +66 -0
  32. isar/robot/robot_status.py +61 -0
  33. isar/robot/robot_stop_mission.py +68 -0
  34. isar/robot/robot_upload_inspection.py +75 -0
  35. isar/script.py +171 -0
  36. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  37. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +32 -0
  38. isar/services/service_connections/mqtt/robot_info_publisher.py +4 -3
  39. isar/services/service_connections/persistent_memory.py +69 -0
  40. isar/services/utilities/mqtt_utilities.py +93 -0
  41. isar/services/utilities/robot_utilities.py +20 -0
  42. isar/services/utilities/scheduling_utilities.py +393 -65
  43. isar/state_machine/state_machine.py +227 -486
  44. isar/state_machine/states/__init__.py +0 -7
  45. isar/state_machine/states/await_next_mission.py +114 -0
  46. isar/state_machine/states/blocked_protective_stop.py +60 -0
  47. isar/state_machine/states/going_to_lockdown.py +95 -0
  48. isar/state_machine/states/going_to_recharging.py +92 -0
  49. isar/state_machine/states/home.py +115 -0
  50. isar/state_machine/states/intervention_needed.py +77 -0
  51. isar/state_machine/states/lockdown.py +38 -0
  52. isar/state_machine/states/maintenance.py +36 -0
  53. isar/state_machine/states/monitor.py +137 -166
  54. isar/state_machine/states/offline.py +60 -0
  55. isar/state_machine/states/paused.py +92 -23
  56. isar/state_machine/states/pausing.py +48 -0
  57. isar/state_machine/states/pausing_return_home.py +48 -0
  58. isar/state_machine/states/recharging.py +80 -0
  59. isar/state_machine/states/resuming.py +57 -0
  60. isar/state_machine/states/resuming_return_home.py +64 -0
  61. isar/state_machine/states/return_home_paused.py +109 -0
  62. isar/state_machine/states/returning_home.py +217 -0
  63. isar/state_machine/states/stopping.py +61 -0
  64. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  65. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  66. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  67. isar/state_machine/states/stopping_return_home.py +77 -0
  68. isar/state_machine/states/unknown_status.py +72 -0
  69. isar/state_machine/states_enum.py +22 -5
  70. isar/state_machine/transitions/mission.py +192 -0
  71. isar/state_machine/transitions/return_home.py +106 -0
  72. isar/state_machine/transitions/robot_status.py +80 -0
  73. isar/state_machine/utils/common_event_handlers.py +73 -0
  74. isar/storage/blob_storage.py +71 -45
  75. isar/storage/local_storage.py +28 -14
  76. isar/storage/storage_interface.py +28 -6
  77. isar/storage/uploader.py +184 -55
  78. isar/storage/utilities.py +35 -27
  79. isar-1.34.9.dist-info/METADATA +496 -0
  80. isar-1.34.9.dist-info/RECORD +135 -0
  81. {isar-1.15.0.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
  82. isar-1.34.9.dist-info/entry_points.txt +3 -0
  83. robot_interface/models/exceptions/__init__.py +0 -7
  84. robot_interface/models/exceptions/robot_exceptions.py +274 -4
  85. robot_interface/models/initialize/__init__.py +0 -1
  86. robot_interface/models/inspection/__init__.py +0 -13
  87. robot_interface/models/inspection/inspection.py +43 -34
  88. robot_interface/models/mission/mission.py +18 -14
  89. robot_interface/models/mission/status.py +20 -25
  90. robot_interface/models/mission/task.py +156 -92
  91. robot_interface/models/robots/battery_state.py +6 -0
  92. robot_interface/models/robots/media.py +13 -0
  93. robot_interface/models/robots/robot_model.py +7 -7
  94. robot_interface/robot_interface.py +135 -66
  95. robot_interface/telemetry/mqtt_client.py +84 -12
  96. robot_interface/telemetry/payloads.py +111 -12
  97. robot_interface/utilities/json_service.py +7 -1
  98. isar/config/predefined_missions/default_turtlebot.json +0 -110
  99. isar/config/predefined_poses/__init__.py +0 -0
  100. isar/config/predefined_poses/predefined_poses.py +0 -616
  101. isar/config/settings.env +0 -26
  102. isar/mission_planner/sequential_task_selector.py +0 -23
  103. isar/mission_planner/task_selector_interface.py +0 -31
  104. isar/models/communication/__init__.py +0 -0
  105. isar/models/communication/message.py +0 -12
  106. isar/models/communication/queues/__init__.py +0 -4
  107. isar/models/communication/queues/queue_io.py +0 -12
  108. isar/models/communication/queues/queue_timeout_error.py +0 -2
  109. isar/models/communication/queues/queues.py +0 -19
  110. isar/models/communication/queues/status_queue.py +0 -20
  111. isar/models/mission_metadata/__init__.py +0 -0
  112. isar/services/readers/__init__.py +0 -0
  113. isar/services/readers/base_reader.py +0 -37
  114. isar/services/service_connections/mqtt/robot_status_publisher.py +0 -93
  115. isar/services/service_connections/stid/__init__.py +0 -0
  116. isar/services/service_connections/stid/stid_service.py +0 -45
  117. isar/services/utilities/queue_utilities.py +0 -39
  118. isar/state_machine/states/idle.py +0 -40
  119. isar/state_machine/states/initialize.py +0 -60
  120. isar/state_machine/states/initiate.py +0 -129
  121. isar/state_machine/states/off.py +0 -18
  122. isar/state_machine/states/stop.py +0 -78
  123. isar/storage/slimm_storage.py +0 -181
  124. isar-1.15.0.dist-info/METADATA +0 -417
  125. isar-1.15.0.dist-info/RECORD +0 -113
  126. robot_interface/models/initialize/initialize_params.py +0 -9
  127. robot_interface/models/mission/step.py +0 -211
  128. {isar-1.15.0.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
  129. {isar-1.15.0.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
isar/__init__.py CHANGED
@@ -1,6 +1,3 @@
1
- from pkg_resources import DistributionNotFound, get_distribution
1
+ from importlib.metadata import version
2
2
 
3
- try:
4
- __version__ = get_distribution(__name__).version
5
- except DistributionNotFound:
6
- pass # package is not installed
3
+ __version__ = version(__package__ or __name__)
isar/apis/api.py CHANGED
@@ -1,66 +1,70 @@
1
+ import json
1
2
  import logging
2
- import os
3
+ import time
4
+ from datetime import datetime, timezone
3
5
  from http import HTTPStatus
4
6
  from logging import Logger
5
7
  from typing import List, Union
6
8
 
7
9
  import click
8
10
  import uvicorn
9
- from fastapi import FastAPI, Request, Security
11
+ from fastapi import FastAPI, Security
10
12
  from fastapi.middleware.cors import CORSMiddleware
11
13
  from fastapi.routing import APIRouter
12
- from injector import inject
13
- from opencensus.ext.azure.trace_exporter import AzureExporter
14
- from opencensus.trace.attributes_helper import COMMON_ATTRIBUTES
15
- from opencensus.trace.samplers import ProbabilitySampler
16
- from opencensus.trace.span import SpanKind
17
- from opencensus.trace.tracer import Tracer
18
14
  from pydantic import AnyHttpUrl
19
15
 
20
16
  from isar.apis.models.models import ControlMissionResponse, StartMissionResponse
17
+ from isar.apis.robot_control.robot_controller import RobotController
21
18
  from isar.apis.schedule.scheduling_controller import SchedulingController
22
19
  from isar.apis.security.authentication import Authenticator
23
- from isar.config.configuration_error import ConfigurationError
24
- from isar.config.keyvault.keyvault_error import KeyvaultError
25
20
  from isar.config.keyvault.keyvault_service import Keyvault
26
21
  from isar.config.settings import settings
27
-
28
- HTTP_URL = COMMON_ATTRIBUTES["HTTP_URL"]
29
- HTTP_STATUS_CODE = COMMON_ATTRIBUTES["HTTP_STATUS_CODE"]
22
+ from robot_interface.telemetry.mqtt_client import MqttClientInterface
23
+ from robot_interface.telemetry.payloads import StartUpMessagePayload
24
+ from robot_interface.utilities.json_service import EnhancedJSONEncoder
30
25
 
31
26
 
32
27
  class API:
33
- @inject
34
28
  def __init__(
35
29
  self,
36
30
  authenticator: Authenticator,
37
31
  scheduling_controller: SchedulingController,
38
- keyvault_client: Keyvault,
32
+ robot_controller: RobotController,
33
+ keyvault: Keyvault,
34
+ mqtt_publisher: MqttClientInterface,
39
35
  port: int = settings.API_PORT,
40
- azure_ai_logging_enabled: bool = settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED,
41
36
  ) -> None:
42
37
  self.authenticator: Authenticator = authenticator
43
38
  self.scheduling_controller: SchedulingController = scheduling_controller
44
- self.keyvault_client: Keyvault = keyvault_client
39
+ self.robot_controller: RobotController = robot_controller
40
+ self.keyvault: Keyvault = keyvault
45
41
  self.host: str = "0.0.0.0" # Locking uvicorn to use 0.0.0.0
46
42
  self.port: int = port
47
- self.azure_ai_logging_enabled: bool = azure_ai_logging_enabled
43
+ self.mqtt_publisher: MqttClientInterface = mqtt_publisher
48
44
 
49
45
  self.logger: Logger = logging.getLogger("api")
50
46
 
51
47
  self.app: FastAPI = self._create_app()
48
+ self.server = self._setup_server()
52
49
 
53
50
  def get_app(self) -> FastAPI:
54
51
  return self.app
55
52
 
56
- def run_app(self) -> None:
57
- uvicorn.run(
53
+ def _setup_server(self) -> uvicorn.Server:
54
+ config = uvicorn.Config(
58
55
  self.app,
59
56
  port=self.port,
60
57
  host=self.host,
61
58
  reload=False,
62
59
  log_config=None,
63
60
  )
61
+ return uvicorn.Server(config)
62
+
63
+ def wait_for_api_server_ready(self) -> None:
64
+ while not self.server.started:
65
+ time.sleep(0.01)
66
+ self.logger.info("Uvicorn server has been started")
67
+ self._publish_startup_message()
64
68
 
65
69
  def _create_app(self) -> FastAPI:
66
70
  tags_metadata = [
@@ -71,7 +75,10 @@ class API:
71
75
  ]
72
76
  app = FastAPI(
73
77
  openapi_tags=tags_metadata,
74
- on_startup=[self.authenticator.load_config, self._log_startup_message],
78
+ on_startup=[
79
+ self.authenticator.load_config,
80
+ self._log_startup_message,
81
+ ],
75
82
  swagger_ui_oauth2_redirect_url="/oauth2-redirect",
76
83
  swagger_ui_init_oauth={
77
84
  "usePkceWithAuthorizationCodeGrant": True,
@@ -92,13 +99,12 @@ class API:
92
99
  allow_headers=["*"],
93
100
  )
94
101
 
95
- if self.azure_ai_logging_enabled:
96
- self._add_request_logging_middleware(app)
97
-
98
102
  app.include_router(router=self._create_scheduler_router())
99
103
 
100
104
  app.include_router(router=self._create_info_router())
101
105
 
106
+ app.include_router(router=self._create_media_control_router())
107
+
102
108
  return app
103
109
 
104
110
  def _create_scheduler_router(self) -> APIRouter:
@@ -146,6 +152,31 @@ class API:
146
152
  HTTPStatus.CONFLICT.value: {
147
153
  "description": "Conflict - Invalid command in the current state",
148
154
  },
155
+ HTTPStatus.BAD_REQUEST.value: {
156
+ "description": "Bad request - Robot does not have the capabilities to perform the mission",
157
+ },
158
+ HTTPStatus.INTERNAL_SERVER_ERROR.value: {
159
+ "description": "Internal Server Error - Current state of state machine unknown",
160
+ },
161
+ },
162
+ )
163
+ router.add_api_route(
164
+ path="/schedule/return-home",
165
+ endpoint=self.scheduling_controller.return_home,
166
+ methods=["POST"],
167
+ dependencies=[authentication_dependency],
168
+ summary="Start return home mission",
169
+ responses={
170
+ HTTPStatus.OK.value: {
171
+ "description": "Return home mission succesfully started",
172
+ "model": StartMissionResponse,
173
+ },
174
+ HTTPStatus.UNPROCESSABLE_ENTITY.value: {
175
+ "description": "Invalid body - The JSON is incorrect",
176
+ },
177
+ HTTPStatus.CONFLICT.value: {
178
+ "description": "Conflict - Invalid command in the current state",
179
+ },
149
180
  HTTPStatus.INTERNAL_SERVER_ERROR.value: {
150
181
  "description": "Internal Server Error - Current state of state machine unknown",
151
182
  },
@@ -162,9 +193,15 @@ class API:
162
193
  "description": "Mission succesfully stopped",
163
194
  "model": ControlMissionResponse,
164
195
  },
196
+ HTTPStatus.UNPROCESSABLE_ENTITY.value: {
197
+ "description": "Invalid body - The JSON is incorrect",
198
+ },
165
199
  HTTPStatus.CONFLICT.value: {
166
200
  "description": "Conflict - Invalid command in the current state",
167
201
  },
202
+ HTTPStatus.BAD_REQUEST.value: {
203
+ "description": "Bad request - Robot does not have the capabilities to perform the mission",
204
+ },
168
205
  HTTPStatus.INTERNAL_SERVER_ERROR.value: {
169
206
  "description": "Internal Server Error - Current state of state machine unknown",
170
207
  },
@@ -209,38 +246,86 @@ class API:
209
246
  },
210
247
  )
211
248
  router.add_api_route(
212
- path="/schedule/drive-to",
213
- endpoint=self.scheduling_controller.drive_to,
249
+ path="/schedule/lockdown",
250
+ endpoint=self.scheduling_controller.lockdown,
251
+ methods=["POST"],
252
+ dependencies=[authentication_dependency],
253
+ summary="Send the robot to dock and ensure that it stays there",
254
+ responses={
255
+ HTTPStatus.OK.value: {"description": "Robot going to dock"},
256
+ HTTPStatus.CONFLICT.value: {
257
+ "description": "Conflict - Invalid command in the current state"
258
+ },
259
+ HTTPStatus.INTERNAL_SERVER_ERROR.value: {
260
+ "description": "Internal Server Error - Current state of state machine unknown"
261
+ },
262
+ },
263
+ )
264
+ router.add_api_route(
265
+ path="/schedule/release-lockdown",
266
+ endpoint=self.scheduling_controller.release_lockdown,
214
267
  methods=["POST"],
215
268
  dependencies=[authentication_dependency],
216
- summary="Drive to the provided pose",
269
+ summary="Allow the robot to start missions again",
217
270
  responses={
218
271
  HTTPStatus.OK.value: {
219
- "description": "Drive to succesfully started",
272
+ "description": "Robot is able to receive missions again"
220
273
  },
221
274
  HTTPStatus.CONFLICT.value: {
222
- "description": "Conflict - Invalid command in the current state",
275
+ "description": "Conflict - Invalid command in the current state"
223
276
  },
224
277
  HTTPStatus.INTERNAL_SERVER_ERROR.value: {
225
- "description": "Internal Server Error - Current state of state machine unknown",
278
+ "description": "Internal Server Error - Current state of state machine unknown"
279
+ },
280
+ },
281
+ )
282
+ router.add_api_route(
283
+ path="/schedule/maintenance-mode",
284
+ endpoint=self.scheduling_controller.set_maintenance_mode,
285
+ methods=["POST"],
286
+ dependencies=[authentication_dependency],
287
+ summary="Place the robot in maintenance mode, where it will not run missions before begin released from maintenance mode",
288
+ responses={
289
+ HTTPStatus.OK.value: {
290
+ "description": "Robot is in maintenance mode and cannot receive missions"
291
+ },
292
+ HTTPStatus.CONFLICT.value: {"description": "Conflict"},
293
+ HTTPStatus.INTERNAL_SERVER_ERROR.value: {
294
+ "description": "Internal Server Error"
295
+ },
296
+ },
297
+ )
298
+ router.add_api_route(
299
+ path="/schedule/release-maintenance-mode",
300
+ endpoint=self.scheduling_controller.release_maintenance_mode,
301
+ methods=["POST"],
302
+ dependencies=[authentication_dependency],
303
+ summary="Allow the robot to start missions again",
304
+ responses={
305
+ HTTPStatus.OK.value: {
306
+ "description": "Robot is able to receive missions again"
307
+ },
308
+ HTTPStatus.CONFLICT.value: {"description": "Conflict"},
309
+ HTTPStatus.INTERNAL_SERVER_ERROR.value: {
310
+ "description": "Internal Server Error"
226
311
  },
227
312
  },
228
313
  )
229
314
  router.add_api_route(
230
- path="/schedule/start-localization-mission",
231
- endpoint=self.scheduling_controller.start_localization_mission,
315
+ path="/schedule/release-intervention-needed",
316
+ endpoint=self.scheduling_controller.release_intervention_needed,
232
317
  methods=["POST"],
233
318
  dependencies=[authentication_dependency],
234
- summary="Localize at the provided pose",
319
+ summary="Release the intervention needed state",
235
320
  responses={
236
321
  HTTPStatus.OK.value: {
237
- "description": "Localization succesfully started",
322
+ "description": "Robot released from intervention needed state"
238
323
  },
239
324
  HTTPStatus.CONFLICT.value: {
240
- "description": "Conflict - Invalid command in the current state",
325
+ "description": "Conflict - Invalid command in the current state"
241
326
  },
242
327
  HTTPStatus.INTERNAL_SERVER_ERROR.value: {
243
- "description": "Internal Server Error - Current state of state machine unknown",
328
+ "description": "Internal Server Error - Current state of state machine unknown"
244
329
  },
245
330
  },
246
331
  )
@@ -254,7 +339,7 @@ class API:
254
339
 
255
340
  router.add_api_route(
256
341
  path="/info/robot-settings",
257
- endpoint=self.scheduling_controller.get_info,
342
+ endpoint=self.robot_controller.get_info,
258
343
  methods=["GET"],
259
344
  dependencies=[authentication_dependency],
260
345
  summary="Information about the robot-settings",
@@ -262,6 +347,29 @@ class API:
262
347
 
263
348
  return router
264
349
 
350
+ def _create_media_control_router(self) -> APIRouter:
351
+ router: APIRouter = APIRouter(tags=["Media"])
352
+
353
+ authentication_dependency: Security = Security(self.authenticator.get_scheme())
354
+
355
+ router.add_api_route(
356
+ path="/media/media-stream-config",
357
+ endpoint=self.robot_controller.generate_media_config,
358
+ methods=["GET"],
359
+ dependencies=[authentication_dependency],
360
+ summary="Generates a media stream connection config",
361
+ responses={
362
+ HTTPStatus.OK.value: {
363
+ "description": "Media stream was successfully generated",
364
+ },
365
+ HTTPStatus.NO_CONTENT.value: {
366
+ "description": "Robot has no media config",
367
+ },
368
+ },
369
+ )
370
+
371
+ return router
372
+
265
373
  def _log_startup_message(self) -> None:
266
374
  address_format = "%s://%s:%d/docs"
267
375
  message = f"Uvicorn running on {address_format} (Press CTRL+C to quit)"
@@ -279,35 +387,20 @@ class API:
279
387
  extra={"color_message": color_message},
280
388
  )
281
389
 
282
- def _add_request_logging_middleware(self, app: FastAPI) -> None:
283
- connection_string: str
284
- try:
285
- connection_string = self.keyvault_client.get_secret(
286
- "application-insights-connection-string"
287
- ).value
288
- except KeyvaultError:
289
- message: str = (
290
- "Missing connection string for Application Insights in key vault. "
291
- )
292
- self.logger.critical(message)
293
- raise ConfigurationError(message)
294
-
295
- @app.middleware("http")
296
- async def middlewareOpencensus(request: Request, call_next):
297
- tracer = Tracer(
298
- exporter=AzureExporter(connection_string=connection_string),
299
- sampler=ProbabilitySampler(1.0),
300
- )
301
- with tracer.span("main") as span:
302
- span.span_kind = SpanKind.SERVER
390
+ def _publish_startup_message(self) -> None:
391
+ if not self.mqtt_publisher:
392
+ return
303
393
 
304
- response = await call_next(request)
394
+ payload: StartUpMessagePayload = StartUpMessagePayload(
395
+ isar_id=settings.ISAR_ID,
396
+ timestamp=datetime.now(timezone.utc),
397
+ )
305
398
 
306
- tracer.add_attribute_to_current_span(
307
- attribute_key=HTTP_STATUS_CODE, attribute_value=response.status_code
308
- )
309
- tracer.add_attribute_to_current_span(
310
- attribute_key=HTTP_URL, attribute_value=str(request.url)
311
- )
399
+ self.logger.info("Publishing startup message to MQTT broker")
312
400
 
313
- return response
401
+ self.mqtt_publisher.publish(
402
+ topic=settings.TOPIC_ISAR_STARTUP,
403
+ payload=json.dumps(payload, cls=EnhancedJSONEncoder),
404
+ qos=1,
405
+ retain=True,
406
+ )
@@ -1 +0,0 @@
1
- from .models import InputPose, StartMissionResponse
@@ -3,16 +3,14 @@ from typing import List, Optional
3
3
  from alitra import Frame, Orientation, Pose, Position
4
4
  from pydantic import BaseModel, Field
5
5
 
6
-
7
- class StepResponse(BaseModel):
8
- id: str
9
- type: str
6
+ from robot_interface.models.mission.task import TaskTypes
10
7
 
11
8
 
12
9
  class TaskResponse(BaseModel):
13
10
  id: str
14
- tag_id: Optional[str]
15
- steps: List[StepResponse]
11
+ tag_id: Optional[str] = None
12
+ inspection_id: Optional[str] = None
13
+ type: TaskTypes
16
14
 
17
15
 
18
16
  class StartMissionResponse(BaseModel):
@@ -21,12 +19,24 @@ class StartMissionResponse(BaseModel):
21
19
 
22
20
 
23
21
  class ControlMissionResponse(BaseModel):
24
- mission_id: str
25
- mission_status: str
26
- task_id: str
27
- task_status: str
28
- step_id: str
29
- step_status: str
22
+ success: bool
23
+ failure_reason: Optional[str] = None
24
+
25
+
26
+ class MissionStartResponse(BaseModel):
27
+ mission_id: Optional[str] = None
28
+ mission_started: bool
29
+ mission_not_started_reason: Optional[str] = None
30
+
31
+
32
+ class LockdownResponse(BaseModel):
33
+ lockdown_started: bool
34
+ failure_reason: Optional[str] = None
35
+
36
+
37
+ class MaintenanceResponse(BaseModel):
38
+ is_maintenance_mode: bool
39
+ failure_reason: Optional[str] = None
30
40
 
31
41
 
32
42
  class RobotInfoResponse(BaseModel):