isar 1.20.2__py3-none-any.whl → 1.34.13__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 (134) hide show
  1. isar/apis/api.py +135 -86
  2. isar/apis/models/__init__.py +0 -1
  3. isar/apis/models/models.py +21 -11
  4. isar/apis/models/start_mission_definition.py +115 -170
  5. isar/apis/robot_control/robot_controller.py +41 -0
  6. isar/apis/schedule/scheduling_controller.py +123 -187
  7. isar/apis/security/authentication.py +5 -5
  8. isar/config/certs/ca-cert.pem +33 -31
  9. isar/config/keyvault/keyvault_service.py +4 -2
  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/settings.py +74 -117
  14. isar/eventhandlers/eventhandler.py +123 -0
  15. isar/models/events.py +184 -0
  16. isar/models/status.py +22 -0
  17. isar/modules.py +117 -200
  18. isar/robot/robot.py +383 -0
  19. isar/robot/robot_battery.py +60 -0
  20. isar/robot/robot_monitor_mission.py +357 -0
  21. isar/robot/robot_pause_mission.py +74 -0
  22. isar/robot/robot_resume_mission.py +67 -0
  23. isar/robot/robot_start_mission.py +66 -0
  24. isar/robot/robot_status.py +61 -0
  25. isar/robot/robot_stop_mission.py +68 -0
  26. isar/robot/robot_upload_inspection.py +75 -0
  27. isar/script.py +58 -41
  28. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  29. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
  30. isar/services/service_connections/mqtt/robot_info_publisher.py +3 -3
  31. isar/services/service_connections/persistent_memory.py +69 -0
  32. isar/services/utilities/mqtt_utilities.py +93 -0
  33. isar/services/utilities/robot_utilities.py +20 -0
  34. isar/services/utilities/scheduling_utilities.py +386 -100
  35. isar/state_machine/state_machine.py +242 -539
  36. isar/state_machine/states/__init__.py +0 -8
  37. isar/state_machine/states/await_next_mission.py +114 -0
  38. isar/state_machine/states/blocked_protective_stop.py +60 -0
  39. isar/state_machine/states/going_to_lockdown.py +95 -0
  40. isar/state_machine/states/going_to_recharging.py +92 -0
  41. isar/state_machine/states/home.py +115 -0
  42. isar/state_machine/states/intervention_needed.py +77 -0
  43. isar/state_machine/states/lockdown.py +38 -0
  44. isar/state_machine/states/maintenance.py +43 -0
  45. isar/state_machine/states/monitor.py +137 -247
  46. isar/state_machine/states/offline.py +51 -53
  47. isar/state_machine/states/paused.py +92 -23
  48. isar/state_machine/states/pausing.py +48 -0
  49. isar/state_machine/states/pausing_return_home.py +48 -0
  50. isar/state_machine/states/recharging.py +80 -0
  51. isar/state_machine/states/resuming.py +57 -0
  52. isar/state_machine/states/resuming_return_home.py +64 -0
  53. isar/state_machine/states/return_home_paused.py +109 -0
  54. isar/state_machine/states/returning_home.py +217 -0
  55. isar/state_machine/states/stopping.py +69 -0
  56. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  57. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  58. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  59. isar/state_machine/states/stopping_paused_mission.py +36 -0
  60. isar/state_machine/states/stopping_paused_return_home.py +59 -0
  61. isar/state_machine/states/stopping_return_home.py +59 -0
  62. isar/state_machine/states/unknown_status.py +74 -0
  63. isar/state_machine/states_enum.py +23 -5
  64. isar/state_machine/transitions/mission.py +225 -0
  65. isar/state_machine/transitions/return_home.py +108 -0
  66. isar/state_machine/transitions/robot_status.py +87 -0
  67. isar/state_machine/utils/common_event_handlers.py +138 -0
  68. isar/storage/blob_storage.py +70 -52
  69. isar/storage/local_storage.py +25 -12
  70. isar/storage/storage_interface.py +28 -7
  71. isar/storage/uploader.py +174 -55
  72. isar/storage/utilities.py +32 -29
  73. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/METADATA +119 -123
  74. isar-1.34.13.dist-info/RECORD +120 -0
  75. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/WHEEL +1 -1
  76. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/entry_points.txt +1 -0
  77. robot_interface/models/exceptions/robot_exceptions.py +91 -41
  78. robot_interface/models/inspection/__init__.py +0 -13
  79. robot_interface/models/inspection/inspection.py +42 -33
  80. robot_interface/models/mission/mission.py +14 -15
  81. robot_interface/models/mission/status.py +20 -26
  82. robot_interface/models/mission/task.py +154 -121
  83. robot_interface/models/robots/battery_state.py +6 -0
  84. robot_interface/models/robots/media.py +13 -0
  85. robot_interface/models/robots/robot_model.py +7 -7
  86. robot_interface/robot_interface.py +119 -84
  87. robot_interface/telemetry/mqtt_client.py +74 -12
  88. robot_interface/telemetry/payloads.py +91 -13
  89. robot_interface/utilities/json_service.py +7 -1
  90. isar/config/configuration_error.py +0 -2
  91. isar/config/keyvault/keyvault_error.py +0 -2
  92. isar/config/predefined_mission_definition/__init__.py +0 -0
  93. isar/config/predefined_mission_definition/default_exr.json +0 -51
  94. isar/config/predefined_mission_definition/default_mission.json +0 -91
  95. isar/config/predefined_mission_definition/default_turtlebot.json +0 -124
  96. isar/config/predefined_missions/__init__.py +0 -0
  97. isar/config/predefined_missions/default.json +0 -92
  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 -25
  102. isar/mission_planner/__init__.py +0 -0
  103. isar/mission_planner/local_planner.py +0 -82
  104. isar/mission_planner/mission_planner_interface.py +0 -26
  105. isar/mission_planner/sequential_task_selector.py +0 -23
  106. isar/mission_planner/task_selector_interface.py +0 -31
  107. isar/models/communication/__init__.py +0 -0
  108. isar/models/communication/message.py +0 -12
  109. isar/models/communication/queues/__init__.py +0 -4
  110. isar/models/communication/queues/queue_io.py +0 -12
  111. isar/models/communication/queues/queue_timeout_error.py +0 -2
  112. isar/models/communication/queues/queues.py +0 -19
  113. isar/models/communication/queues/status_queue.py +0 -20
  114. isar/models/mission_metadata/__init__.py +0 -0
  115. isar/services/auth/__init__.py +0 -0
  116. isar/services/auth/azure_credentials.py +0 -14
  117. isar/services/readers/__init__.py +0 -0
  118. isar/services/readers/base_reader.py +0 -37
  119. isar/services/service_connections/request_handler.py +0 -153
  120. isar/services/service_connections/stid/__init__.py +0 -0
  121. isar/services/utilities/queue_utilities.py +0 -39
  122. isar/services/utilities/threaded_request.py +0 -68
  123. isar/state_machine/states/idle.py +0 -85
  124. isar/state_machine/states/initialize.py +0 -71
  125. isar/state_machine/states/initiate.py +0 -142
  126. isar/state_machine/states/off.py +0 -18
  127. isar/state_machine/states/stop.py +0 -95
  128. isar/storage/slimm_storage.py +0 -191
  129. isar-1.20.2.dist-info/RECORD +0 -116
  130. robot_interface/models/initialize/__init__.py +0 -1
  131. robot_interface/models/initialize/initialize_params.py +0 -9
  132. robot_interface/models/mission/step.py +0 -234
  133. {isar-1.20.2.dist-info → isar-1.34.13.dist-info/licenses}/LICENSE +0 -0
  134. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/top_level.txt +0 -0
@@ -1,230 +1,175 @@
1
1
  import time
2
2
  from enum import Enum
3
- from typing import Any, Dict, List, Optional, Union
3
+ from typing import List, Optional
4
4
 
5
- from alitra import Position, Pose, Orientation, Frame
6
5
  from pydantic import BaseModel, Field
7
6
 
8
7
  from isar.apis.models.models import InputPose, InputPosition
9
8
  from isar.config.settings import settings
10
- from isar.mission_planner.mission_planner_interface import MissionPlannerError
11
9
  from robot_interface.models.mission.mission import Mission
12
- from robot_interface.models.mission.step import (
13
- STEPS,
14
- DockingProcedure,
15
- DriveToPose,
16
- Localize,
10
+ from robot_interface.models.mission.task import (
11
+ TASKS,
17
12
  RecordAudio,
18
13
  ReturnToHome,
14
+ TakeCO2Measurement,
19
15
  TakeImage,
20
16
  TakeThermalImage,
21
17
  TakeThermalVideo,
22
18
  TakeVideo,
19
+ ZoomDescription,
23
20
  )
24
- from robot_interface.models.mission.task import Task
21
+ from robot_interface.utilities.uuid_string_factory import uuid4_string
25
22
 
26
23
 
27
24
  class InspectionTypes(str, Enum):
28
- image: str = "Image"
29
- thermal_image: str = "ThermalImage"
30
- video: str = "Video"
31
- thermal_video: str = "ThermalVideo"
32
- audio: str = "Audio"
25
+ image = "Image"
26
+ thermal_image = "ThermalImage"
27
+ video = "Video"
28
+ thermal_video = "ThermalVideo"
29
+ audio = "Audio"
30
+ co2_measurement = "CO2Measurement"
33
31
 
34
32
 
35
33
  class TaskType(str, Enum):
36
- Inspection: str = "inspection"
37
- DriveTo: str = "drive_to"
38
- Localization: str = "localization"
39
- ReturnToHome: str = "return_to_home"
40
- Dock: str = "dock"
34
+ Inspection = "inspection"
35
+ ReturnToHome = "return_to_home"
41
36
 
42
37
 
43
38
  class StartMissionInspectionDefinition(BaseModel):
44
39
  type: InspectionTypes = Field(default=InspectionTypes.image)
45
40
  inspection_target: InputPosition
46
- analysis_type: Optional[str] = None
41
+ inspection_description: Optional[str] = None
47
42
  duration: Optional[float] = None
48
- metadata: Optional[dict] = None
49
- id: Optional[str] = None
50
43
 
51
44
 
52
45
  class StartMissionTaskDefinition(BaseModel):
46
+ id: Optional[str] = None
53
47
  type: TaskType = Field(default=TaskType.Inspection)
54
48
  pose: InputPose
55
- inspections: List[StartMissionInspectionDefinition]
49
+ inspection: Optional[StartMissionInspectionDefinition] = None
56
50
  tag: Optional[str] = None
57
- id: Optional[str] = None
51
+ zoom: Optional[ZoomDescription] = None
58
52
 
59
53
 
60
54
  class StartMissionDefinition(BaseModel):
61
- tasks: List[StartMissionTaskDefinition]
62
55
  id: Optional[str] = None
56
+ tasks: List[StartMissionTaskDefinition]
63
57
  name: Optional[str] = None
64
58
  start_pose: Optional[InputPose] = None
65
59
 
66
60
 
67
- def to_isar_mission(mission_definition: StartMissionDefinition) -> Mission:
68
- isar_tasks: List[Task] = []
69
- all_steps_in_mission: List[STEPS] = []
70
-
71
- for task in mission_definition.tasks:
72
- steps: List[STEPS] = generate_steps(task)
73
- all_steps_in_mission.extend(steps)
74
-
75
- isar_task: Task = Task(steps=steps, tag_id=task.tag)
76
- if task.id:
77
- isar_task.id = task.id
78
- isar_tasks.append(isar_task)
79
-
80
- if not isar_tasks:
81
- raise MissionPlannerError("Mission does not contain any valid tasks")
82
-
83
- check_for_duplicate_ids(isar_tasks)
84
- check_for_duplicate_ids(all_steps_in_mission)
61
+ class StopMissionDefinition(BaseModel):
62
+ mission_id: Optional[str] = None
85
63
 
86
- isar_mission: Mission = Mission(tasks=isar_tasks)
87
64
 
88
- if mission_definition.name:
89
- isar_mission.name = mission_definition.name
90
- else:
91
- isar_mission.name = _build_mission_name()
92
-
93
- if mission_definition.id:
94
- isar_mission.id = mission_definition.id
95
-
96
- if mission_definition.start_pose:
97
- input_pose: InputPose = mission_definition.start_pose
98
- input_frame: Frame = Frame(name=input_pose.frame_name)
99
- input_position: Position = Position(
100
- input_pose.position.x,
101
- input_pose.position.y,
102
- input_pose.position.z,
103
- input_frame,
104
- )
105
- input_orientation: Orientation = Orientation(
106
- input_pose.orientation.x,
107
- input_pose.orientation.y,
108
- input_pose.orientation.z,
109
- input_pose.orientation.w,
110
- input_frame,
111
- )
112
- isar_mission.start_pose = Pose(
113
- position=input_position, orientation=input_orientation, frame=input_frame
114
- )
65
+ class MissionFormatError(Exception):
66
+ pass
115
67
 
116
- return isar_mission
117
68
 
69
+ def to_isar_mission(
70
+ start_mission_definition: StartMissionDefinition,
71
+ ) -> Mission:
72
+ isar_tasks: List[TASKS] = []
118
73
 
119
- def check_for_duplicate_ids(items: Union[List[Task], List[STEPS]]):
120
- duplicate_ids = get_duplicate_ids(items=items)
121
- if len(duplicate_ids) > 0:
122
- raise MissionPlannerError(
123
- f"Failed to create as there were duplicate IDs which is not allowed "
124
- f"({duplicate_ids})"
125
- )
126
-
127
-
128
- def generate_steps(task) -> List[STEPS]:
129
- steps: List[STEPS] = []
130
- try:
131
- match task.type:
132
- case TaskType.Inspection:
133
- steps.extend(generate_steps_for_inspection_task(task=task))
134
- case TaskType.DriveTo:
135
- steps.append(generate_steps_for_drive_to_task(task=task))
136
- case TaskType.Localization:
137
- steps.append(generate_steps_for_localization_task(task=task))
138
- case TaskType.ReturnToHome:
139
- steps.append(generate_steps_for_return_to_home_task(task=task))
140
- case TaskType.Dock:
141
- steps.append(generate_steps_for_dock_task())
142
- except ValueError as e:
143
- raise MissionPlannerError(f"Failed to create task: {str(e)}")
144
-
145
- return steps
146
-
147
-
148
- def generate_steps_for_inspection_task(task: StartMissionTaskDefinition) -> List[STEPS]:
149
- drive_step: DriveToPose = DriveToPose(pose=task.pose.to_alitra_pose())
150
-
151
- inspection_steps: List[STEPS] = [
152
- create_inspection_step(
153
- inspection_type=inspection.type,
154
- duration=inspection.duration,
155
- target=inspection.inspection_target.to_alitra_position(),
156
- tag_id=task.tag,
157
- metadata=inspection.metadata,
158
- id=inspection.id,
159
- )
160
- for inspection in task.inspections
161
- ]
162
-
163
- return [drive_step, *inspection_steps]
164
-
165
-
166
- def generate_steps_for_drive_to_task(task: StartMissionTaskDefinition) -> DriveToPose:
167
- return DriveToPose(pose=task.pose.to_alitra_pose())
168
-
169
-
170
- def generate_steps_for_localization_task(task: StartMissionTaskDefinition) -> Localize:
171
- return Localize(localization_pose=task.pose.to_alitra_pose())
74
+ for task_definition in start_mission_definition.tasks:
75
+ task: TASKS = to_isar_task(task_definition)
76
+ isar_tasks.append(task)
172
77
 
78
+ if not isar_tasks:
79
+ raise MissionFormatError("Mission does not contain any valid tasks")
173
80
 
174
- def generate_steps_for_return_to_home_task(
175
- task: StartMissionTaskDefinition,
176
- ) -> ReturnToHome:
177
- return ReturnToHome(pose=task.pose.to_alitra_pose())
81
+ isar_mission_name: str = (
82
+ start_mission_definition.name
83
+ if start_mission_definition.name
84
+ else _build_mission_name()
85
+ )
178
86
 
87
+ start_pose = None
88
+ if start_mission_definition.start_pose:
89
+ start_pose = start_mission_definition.start_pose.to_alitra_pose()
179
90
 
180
- def generate_steps_for_dock_task() -> DockingProcedure:
181
- return DockingProcedure(behavior="dock")
91
+ id = start_mission_definition.id if start_mission_definition.id else uuid4_string()
182
92
 
93
+ return Mission(
94
+ id=id,
95
+ tasks=isar_tasks,
96
+ name=isar_mission_name,
97
+ start_pose=start_pose,
98
+ )
183
99
 
184
- def create_inspection_step(
185
- inspection_type: InspectionTypes,
186
- duration: float,
187
- target: Position,
188
- tag_id: Optional[str],
189
- metadata: Optional[dict],
190
- id: Optional[str],
191
- ) -> STEPS:
192
- inspection_step_dict: Dict[str, Any] = {
193
- InspectionTypes.image.value: TakeImage(target=target),
194
- InspectionTypes.video.value: TakeVideo(target=target, duration=duration),
195
- InspectionTypes.thermal_image.value: TakeThermalImage(target=target),
196
- InspectionTypes.thermal_video.value: TakeThermalVideo(
197
- target=target, duration=duration
198
- ),
199
- InspectionTypes.audio.value: RecordAudio(target=target, duration=duration),
200
- }
201
100
 
202
- if inspection_type not in inspection_step_dict:
203
- raise ValueError(f"Inspection type '{inspection_type}' not supported")
101
+ def to_isar_task(task_definition: StartMissionTaskDefinition) -> TASKS:
102
+ if task_definition.type == TaskType.Inspection:
103
+ return to_inspection_task(task_definition)
104
+ elif task_definition.type == TaskType.ReturnToHome:
105
+ return ReturnToHome()
204
106
  else:
205
- inspection_step = inspection_step_dict[inspection_type]
206
-
207
- if tag_id:
208
- inspection_step.tag_id = tag_id
209
- if metadata:
210
- inspection_step.metadata = metadata
211
- if id:
212
- inspection_step.id = id
213
-
214
- return inspection_step
107
+ raise MissionFormatError(
108
+ f"Failed to create task: '{task_definition.type}' is not a valid"
109
+ )
215
110
 
216
111
 
217
- def get_duplicate_ids(items: Union[List[Task], List[STEPS]]) -> List[str]:
218
- unique_ids: List[str] = []
219
- duplicate_ids: List[str] = []
220
- for item in items:
221
- id: str = item.id
222
- if id not in unique_ids:
223
- unique_ids.append(id)
224
- else:
225
- duplicate_ids.append(id)
112
+ def to_inspection_task(task_definition: StartMissionTaskDefinition) -> TASKS:
113
+ inspection_definition = task_definition.inspection
226
114
 
227
- return duplicate_ids
115
+ if inspection_definition.type == InspectionTypes.image:
116
+ return TakeImage(
117
+ id=task_definition.id if task_definition.id else uuid4_string(),
118
+ robot_pose=task_definition.pose.to_alitra_pose(),
119
+ tag_id=task_definition.tag,
120
+ inspection_description=task_definition.inspection.inspection_description,
121
+ target=task_definition.inspection.inspection_target.to_alitra_position(),
122
+ zoom=task_definition.zoom,
123
+ )
124
+ elif inspection_definition.type == InspectionTypes.video:
125
+ return TakeVideo(
126
+ id=task_definition.id if task_definition.id else uuid4_string(),
127
+ robot_pose=task_definition.pose.to_alitra_pose(),
128
+ tag_id=task_definition.tag,
129
+ inspection_description=task_definition.inspection.inspection_description,
130
+ target=task_definition.inspection.inspection_target.to_alitra_position(),
131
+ duration=inspection_definition.duration,
132
+ zoom=task_definition.zoom,
133
+ )
134
+ elif inspection_definition.type == InspectionTypes.thermal_image:
135
+ return TakeThermalImage(
136
+ id=task_definition.id if task_definition.id else uuid4_string(),
137
+ robot_pose=task_definition.pose.to_alitra_pose(),
138
+ tag_id=task_definition.tag,
139
+ inspection_description=task_definition.inspection.inspection_description,
140
+ target=task_definition.inspection.inspection_target.to_alitra_position(),
141
+ zoom=task_definition.zoom,
142
+ )
143
+ elif inspection_definition.type == InspectionTypes.thermal_video:
144
+ return TakeThermalVideo(
145
+ id=task_definition.id if task_definition.id else uuid4_string(),
146
+ robot_pose=task_definition.pose.to_alitra_pose(),
147
+ tag_id=task_definition.tag,
148
+ inspection_description=task_definition.inspection.inspection_description,
149
+ target=task_definition.inspection.inspection_target.to_alitra_position(),
150
+ duration=inspection_definition.duration,
151
+ zoom=task_definition.zoom,
152
+ )
153
+ elif inspection_definition.type == InspectionTypes.audio:
154
+ return RecordAudio(
155
+ id=task_definition.id if task_definition.id else uuid4_string(),
156
+ robot_pose=task_definition.pose.to_alitra_pose(),
157
+ tag_id=task_definition.tag,
158
+ inspection_description=task_definition.inspection.inspection_description,
159
+ target=task_definition.inspection.inspection_target.to_alitra_position(),
160
+ duration=inspection_definition.duration,
161
+ )
162
+ elif inspection_definition.type == InspectionTypes.co2_measurement:
163
+ return TakeCO2Measurement(
164
+ id=task_definition.id if task_definition.id else uuid4_string(),
165
+ robot_pose=task_definition.pose.to_alitra_pose(),
166
+ tag_id=task_definition.tag,
167
+ inspection_description=task_definition.inspection.inspection_description,
168
+ )
169
+ else:
170
+ raise ValueError(
171
+ f"Inspection type '{inspection_definition.type}' not supported"
172
+ )
228
173
 
229
174
 
230
175
  def _build_mission_name() -> str:
@@ -0,0 +1,41 @@
1
+ import logging
2
+
3
+ from fastapi import HTTPException
4
+ from opentelemetry import trace
5
+
6
+ from isar.apis.models.models import RobotInfoResponse
7
+ from isar.config.settings import robot_settings, settings
8
+ from isar.services.utilities.robot_utilities import RobotUtilities
9
+ from robot_interface.models.robots.media import MediaConfig
10
+
11
+ tracer = trace.get_tracer(__name__)
12
+
13
+
14
+ class RobotController:
15
+ def __init__(
16
+ self,
17
+ robot_utilities: RobotUtilities,
18
+ ):
19
+ self.robot_utilities: RobotUtilities = robot_utilities
20
+ self.logger = logging.getLogger("api")
21
+
22
+ @tracer.start_as_current_span("generate_media_config")
23
+ def generate_media_config(self) -> MediaConfig:
24
+ media_config: MediaConfig = self.robot_utilities.generate_media_config()
25
+ if media_config is None:
26
+ raise HTTPException(
27
+ status_code=204,
28
+ detail="Robot has no media config",
29
+ )
30
+ return media_config
31
+
32
+ @tracer.start_as_current_span("get_info")
33
+ def get_info(self) -> RobotInfoResponse:
34
+ return RobotInfoResponse(
35
+ robot_package=settings.ROBOT_PACKAGE,
36
+ isar_id=settings.ISAR_ID,
37
+ robot_name=settings.ROBOT_NAME,
38
+ robot_capabilities=robot_settings.CAPABILITIES,
39
+ robot_map_name=settings.DEFAULT_MAP,
40
+ plant_short_name=settings.PLANT_SHORT_NAME,
41
+ )