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
isar/apis/api.py CHANGED
@@ -1,65 +1,70 @@
1
+ import json
1
2
  import logging
3
+ import time
4
+ from datetime import datetime, timezone
2
5
  from http import HTTPStatus
3
6
  from logging import Logger
4
7
  from typing import List, Union
5
8
 
6
9
  import click
7
10
  import uvicorn
8
- from fastapi import FastAPI, Request, Security
11
+ from fastapi import FastAPI, Security
9
12
  from fastapi.middleware.cors import CORSMiddleware
10
13
  from fastapi.routing import APIRouter
11
- from injector import inject
12
- from opencensus.ext.azure.trace_exporter import AzureExporter
13
- from opencensus.trace.attributes_helper import COMMON_ATTRIBUTES
14
- from opencensus.trace.samplers import ProbabilitySampler
15
- from opencensus.trace.span import SpanKind
16
- from opencensus.trace.tracer import Tracer
17
14
  from pydantic import AnyHttpUrl
18
15
 
19
16
  from isar.apis.models.models import ControlMissionResponse, StartMissionResponse
17
+ from isar.apis.robot_control.robot_controller import RobotController
20
18
  from isar.apis.schedule.scheduling_controller import SchedulingController
21
19
  from isar.apis.security.authentication import Authenticator
22
- from isar.config.configuration_error import ConfigurationError
23
- from isar.config.keyvault.keyvault_error import KeyvaultError
24
20
  from isar.config.keyvault.keyvault_service import Keyvault
25
21
  from isar.config.settings import settings
26
-
27
- HTTP_URL = COMMON_ATTRIBUTES["HTTP_URL"]
28
- 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
29
25
 
30
26
 
31
27
  class API:
32
- @inject
33
28
  def __init__(
34
29
  self,
35
30
  authenticator: Authenticator,
36
31
  scheduling_controller: SchedulingController,
37
- keyvault_client: Keyvault,
32
+ robot_controller: RobotController,
33
+ keyvault: Keyvault,
34
+ mqtt_publisher: MqttClientInterface,
38
35
  port: int = settings.API_PORT,
39
- azure_ai_logging_enabled: bool = settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED,
40
36
  ) -> None:
41
37
  self.authenticator: Authenticator = authenticator
42
38
  self.scheduling_controller: SchedulingController = scheduling_controller
43
- self.keyvault_client: Keyvault = keyvault_client
39
+ self.robot_controller: RobotController = robot_controller
40
+ self.keyvault: Keyvault = keyvault
44
41
  self.host: str = "0.0.0.0" # Locking uvicorn to use 0.0.0.0
45
42
  self.port: int = port
46
- self.azure_ai_logging_enabled: bool = azure_ai_logging_enabled
43
+ self.mqtt_publisher: MqttClientInterface = mqtt_publisher
47
44
 
48
45
  self.logger: Logger = logging.getLogger("api")
49
46
 
50
47
  self.app: FastAPI = self._create_app()
48
+ self.server = self._setup_server()
51
49
 
52
50
  def get_app(self) -> FastAPI:
53
51
  return self.app
54
52
 
55
- def run_app(self) -> None:
56
- uvicorn.run(
53
+ def _setup_server(self) -> uvicorn.Server:
54
+ config = uvicorn.Config(
57
55
  self.app,
58
56
  port=self.port,
59
57
  host=self.host,
60
58
  reload=False,
61
59
  log_config=None,
62
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()
63
68
 
64
69
  def _create_app(self) -> FastAPI:
65
70
  tags_metadata = [
@@ -70,7 +75,10 @@ class API:
70
75
  ]
71
76
  app = FastAPI(
72
77
  openapi_tags=tags_metadata,
73
- on_startup=[self.authenticator.load_config, self._log_startup_message],
78
+ on_startup=[
79
+ self.authenticator.load_config,
80
+ self._log_startup_message,
81
+ ],
74
82
  swagger_ui_oauth2_redirect_url="/oauth2-redirect",
75
83
  swagger_ui_init_oauth={
76
84
  "usePkceWithAuthorizationCodeGrant": True,
@@ -91,13 +99,12 @@ class API:
91
99
  allow_headers=["*"],
92
100
  )
93
101
 
94
- if self.azure_ai_logging_enabled:
95
- self._add_request_logging_middleware(app)
96
-
97
102
  app.include_router(router=self._create_scheduler_router())
98
103
 
99
104
  app.include_router(router=self._create_info_router())
100
105
 
106
+ app.include_router(router=self._create_media_control_router())
107
+
101
108
  return app
102
109
 
103
110
  def _create_scheduler_router(self) -> APIRouter:
@@ -145,6 +152,31 @@ class API:
145
152
  HTTPStatus.CONFLICT.value: {
146
153
  "description": "Conflict - Invalid command in the current state",
147
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
+ },
148
180
  HTTPStatus.INTERNAL_SERVER_ERROR.value: {
149
181
  "description": "Internal Server Error - Current state of state machine unknown",
150
182
  },
@@ -161,9 +193,15 @@ class API:
161
193
  "description": "Mission succesfully stopped",
162
194
  "model": ControlMissionResponse,
163
195
  },
196
+ HTTPStatus.UNPROCESSABLE_ENTITY.value: {
197
+ "description": "Invalid body - The JSON is incorrect",
198
+ },
164
199
  HTTPStatus.CONFLICT.value: {
165
200
  "description": "Conflict - Invalid command in the current state",
166
201
  },
202
+ HTTPStatus.BAD_REQUEST.value: {
203
+ "description": "Bad request - Robot does not have the capabilities to perform the mission",
204
+ },
167
205
  HTTPStatus.INTERNAL_SERVER_ERROR.value: {
168
206
  "description": "Internal Server Error - Current state of state machine unknown",
169
207
  },
@@ -208,60 +246,86 @@ class API:
208
246
  },
209
247
  )
210
248
  router.add_api_route(
211
- path="/schedule/drive-to",
212
- endpoint=self.scheduling_controller.drive_to,
249
+ path="/schedule/lockdown",
250
+ endpoint=self.scheduling_controller.lockdown,
213
251
  methods=["POST"],
214
252
  dependencies=[authentication_dependency],
215
- summary="Drive to the provided pose",
253
+ summary="Send the robot to dock and ensure that it stays there",
216
254
  responses={
217
- HTTPStatus.OK.value: {
218
- "description": "Drive to succesfully started",
219
- },
255
+ HTTPStatus.OK.value: {"description": "Robot going to dock"},
220
256
  HTTPStatus.CONFLICT.value: {
221
- "description": "Conflict - Invalid command in the current state",
257
+ "description": "Conflict - Invalid command in the current state"
222
258
  },
223
259
  HTTPStatus.INTERNAL_SERVER_ERROR.value: {
224
- "description": "Internal Server Error - Current state of state machine unknown",
260
+ "description": "Internal Server Error - Current state of state machine unknown"
225
261
  },
226
262
  },
227
263
  )
228
264
  router.add_api_route(
229
- path="/schedule/start-localization-mission",
230
- endpoint=self.scheduling_controller.start_localization_mission,
265
+ path="/schedule/release-lockdown",
266
+ endpoint=self.scheduling_controller.release_lockdown,
231
267
  methods=["POST"],
232
268
  dependencies=[authentication_dependency],
233
- summary="Localize at the provided pose",
269
+ summary="Allow the robot to start missions again",
234
270
  responses={
235
271
  HTTPStatus.OK.value: {
236
- "description": "Localization succesfully started",
272
+ "description": "Robot is able to receive missions again"
237
273
  },
238
274
  HTTPStatus.CONFLICT.value: {
239
- "description": "Conflict - Invalid command in the current state",
275
+ "description": "Conflict - Invalid command in the current state"
240
276
  },
241
277
  HTTPStatus.INTERNAL_SERVER_ERROR.value: {
242
- "description": "Internal Server Error - Current state of state machine unknown",
278
+ "description": "Internal Server Error - Current state of state machine unknown"
243
279
  },
244
280
  },
245
281
  )
246
282
  router.add_api_route(
247
- path="/schedule/move_arm/{arm_pose_literal}",
248
- endpoint=self.scheduling_controller.start_move_arm_mission,
283
+ path="/schedule/maintenance-mode",
284
+ endpoint=self.scheduling_controller.set_maintenance_mode,
249
285
  methods=["POST"],
250
286
  dependencies=[authentication_dependency],
251
- summary="Move arm to the given arm pose literal",
287
+ summary="Place the robot in maintenance mode, where it will not run missions before begin released from maintenance mode",
252
288
  responses={
253
289
  HTTPStatus.OK.value: {
254
- "description": "Move arm mission successfully started",
290
+ "description": "Robot is in maintenance mode and cannot receive missions"
255
291
  },
256
- HTTPStatus.BAD_REQUEST.value: {
257
- "description": "A move arm mission was scheduled on a robot that "
258
- "does not support it or the input was incorrect",
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"
311
+ },
312
+ },
313
+ )
314
+ router.add_api_route(
315
+ path="/schedule/release-intervention-needed",
316
+ endpoint=self.scheduling_controller.release_intervention_needed,
317
+ methods=["POST"],
318
+ dependencies=[authentication_dependency],
319
+ summary="Release the intervention needed state",
320
+ responses={
321
+ HTTPStatus.OK.value: {
322
+ "description": "Robot released from intervention needed state"
259
323
  },
260
324
  HTTPStatus.CONFLICT.value: {
261
- "description": "Conflict - Invalid command in the current state",
325
+ "description": "Conflict - Invalid command in the current state"
262
326
  },
263
327
  HTTPStatus.INTERNAL_SERVER_ERROR.value: {
264
- "description": "Internal Server Error - Current state of state machine unknown",
328
+ "description": "Internal Server Error - Current state of state machine unknown"
265
329
  },
266
330
  },
267
331
  )
@@ -275,7 +339,7 @@ class API:
275
339
 
276
340
  router.add_api_route(
277
341
  path="/info/robot-settings",
278
- endpoint=self.scheduling_controller.get_info,
342
+ endpoint=self.robot_controller.get_info,
279
343
  methods=["GET"],
280
344
  dependencies=[authentication_dependency],
281
345
  summary="Information about the robot-settings",
@@ -283,6 +347,29 @@ class API:
283
347
 
284
348
  return router
285
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
+
286
373
  def _log_startup_message(self) -> None:
287
374
  address_format = "%s://%s:%d/docs"
288
375
  message = f"Uvicorn running on {address_format} (Press CTRL+C to quit)"
@@ -300,35 +387,20 @@ class API:
300
387
  extra={"color_message": color_message},
301
388
  )
302
389
 
303
- def _add_request_logging_middleware(self, app: FastAPI) -> None:
304
- connection_string: str
305
- try:
306
- connection_string = self.keyvault_client.get_secret(
307
- "application-insights-connection-string"
308
- ).value
309
- except KeyvaultError:
310
- message: str = (
311
- "Missing connection string for Application Insights in key vault. "
312
- )
313
- self.logger.critical(message)
314
- raise ConfigurationError(message)
315
-
316
- @app.middleware("http")
317
- async def middlewareOpencensus(request: Request, call_next):
318
- tracer = Tracer(
319
- exporter=AzureExporter(connection_string=connection_string),
320
- sampler=ProbabilitySampler(1.0),
321
- )
322
- with tracer.span("main") as span:
323
- span.span_kind = SpanKind.SERVER
390
+ def _publish_startup_message(self) -> None:
391
+ if not self.mqtt_publisher:
392
+ return
324
393
 
325
- response = await call_next(request)
394
+ payload: StartUpMessagePayload = StartUpMessagePayload(
395
+ isar_id=settings.ISAR_ID,
396
+ timestamp=datetime.now(timezone.utc),
397
+ )
326
398
 
327
- tracer.add_attribute_to_current_span(
328
- attribute_key=HTTP_STATUS_CODE, attribute_value=response.status_code
329
- )
330
- tracer.add_attribute_to_current_span(
331
- attribute_key=HTTP_URL, attribute_value=str(request.url)
332
- )
399
+ self.logger.info("Publishing startup message to MQTT broker")
333
400
 
334
- 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
11
  tag_id: Optional[str] = None
15
- steps: List[StepResponse]
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):