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/config/settings.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  from importlib.resources import as_file, files
3
- from typing import Any, List, Optional
3
+ from typing import Any, List
4
4
 
5
5
  from dotenv import load_dotenv
6
6
  from pydantic import Field, ValidationInfo, field_validator
@@ -8,26 +8,21 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
8
8
 
9
9
  from isar.config import predefined_missions
10
10
  from robot_interface.models.robots.robot_model import RobotModel
11
- from robot_interface.telemetry.payloads import VideoStream
11
+ from robot_interface.telemetry.payloads import DocumentInfo
12
12
 
13
13
 
14
14
  class Settings(BaseSettings):
15
- def __init__(self) -> None:
16
- try:
17
- source = files("isar").joinpath("config").joinpath("settings.env")
18
- with as_file(source) as eml:
19
- env_file = eml
20
- except ModuleNotFoundError:
21
- env_file = None
22
- super().__init__(_env_file=env_file)
15
+ # Endpoint open telemetry will export telemetry in the otlp protocol to
16
+ OPEN_TELEMETRY_OTLP_EXPORTER_ENDPOINT: str = Field(default="http://localhost:4318")
17
+
18
+ # Connection string for Azure Application Insights
19
+ # This is optional and it will use managed identity if not set
20
+ APPLICATIONINSIGHTS_CONNECTION_STRING: str = Field(default="")
23
21
 
24
22
  # Determines which robot package ISAR will attempt to import
25
23
  # Name must match with an installed python package in the local environment
26
24
  ROBOT_PACKAGE: str = Field(default="isar_robot")
27
25
 
28
- # The run mode of the robot (stepwise or full mission)
29
- RUN_MISSION_STEPWISE: bool = Field(default=True)
30
-
31
26
  # Determines the local path in which results from missions are stored
32
27
  LOCAL_STORAGE_PATH: str = Field(default="./results")
33
28
 
@@ -48,20 +43,21 @@ class Settings(BaseSettings):
48
43
  # Name of default map transformation
49
44
  DEFAULT_MAP: str = Field(default="default_map")
50
45
 
51
- # Location of JSON files containing predefined maps
52
- MAPS_FOLDER: str = Field(default="src/isar/config/maps/")
53
-
54
46
  # Determines the number of state transitions that are kept in the log
55
47
  STATE_TRANSITIONS_LOG_LENGTH: int = Field(default=20)
56
48
 
57
- # Number of attempts to initiate a step or mission before cancelling
58
- INITIATE_FAILURE_COUNTER_LIMIT: int = Field(default=10)
59
-
60
- # Number of attempts to request a step status in monitor before cancelling
49
+ # Number of attempts to request a task status in monitor before cancelling
61
50
  REQUEST_STATUS_FAILURE_COUNTER_LIMIT: int = Field(default=3)
62
51
 
52
+ # Time allocated to reconnect when failing to retrieve status due to communication
53
+ # issues
54
+ REQUEST_STATUS_COMMUNICATION_RECONNECT_DELAY: float = Field(default=10)
55
+
56
+ # Number of attempts for state transitions resume and pause if failed
57
+ STATE_TRANSITION_NUM_RETIRES: int = Field(default=10)
58
+
63
59
  # Number of attempts to stop the robot before giving up
64
- STOP_ROBOT_ATTEMPTS_LIMIT: int = Field(default=10)
60
+ STOP_ROBOT_ATTEMPTS_LIMIT: int = Field(default=3)
65
61
 
66
62
  # Number of attempts to stop the robot before giving up
67
63
  UPLOAD_FAILURE_ATTEMPTS_LIMIT: int = Field(default=10)
@@ -70,10 +66,20 @@ class Settings(BaseSettings):
70
66
  UPLOAD_FAILURE_MAX_WAIT: int = Field(default=60)
71
67
 
72
68
  # ISAR telemetry intervals
73
- ROBOT_STATUS_PUBLISH_INTERVAL: float = Field(default=1)
74
69
  ROBOT_HEARTBEAT_PUBLISH_INTERVAL: float = Field(default=1)
75
70
  ROBOT_INFO_PUBLISH_INTERVAL: float = Field(default=5)
71
+ ROBOT_API_BATTERY_POLL_INTERVAL: float = Field(default=5)
76
72
  ROBOT_API_STATUS_POLL_INTERVAL: float = Field(default=5)
73
+ THREAD_CHECK_INTERVAL: float = Field(default=0.01)
74
+
75
+ # Determines the minimum battery level the robot must have to start a mission
76
+ # If it drops below this level it will recharge to the value set by
77
+ # ROBOT_BATTERY_RECHARGE_THRESHOLD before starting new missions
78
+ ROBOT_MISSION_BATTERY_START_THRESHOLD: float = Field(default=25.0)
79
+
80
+ # Determines the minimum battery threshold to consider the robot recharged
81
+ # and ready for more missions, after having run low on charge
82
+ ROBOT_BATTERY_RECHARGE_THRESHOLD: float = Field(default=80.0)
77
83
 
78
84
  # FastAPI host
79
85
  API_HOST_VIEWED_EXTERNALLY: str = Field(default="0.0.0.0")
@@ -81,20 +87,21 @@ class Settings(BaseSettings):
81
87
  # FastAPI port
82
88
  API_PORT: int = Field(default=3000)
83
89
 
90
+ # Determines how long delay time should be allowed before returning home
91
+ RETURN_HOME_DELAY: int = Field(default=10)
92
+
93
+ # Sets how many times the robot should try to return home if a return home fails
94
+ RETURN_HOME_RETRY_LIMIT: int = Field(default=5)
95
+
84
96
  # Determines which mission planner module is used by ISAR
85
97
  MISSION_PLANNER: str = Field(default="local")
86
98
 
87
- # Determines which task selector module is used by ISAR
88
- # Options: [sequential]
89
- TASK_SELECTOR: str = Field(default="sequential")
90
-
91
99
  # Determines which storage modules are used by ISAR
92
100
  # Multiple storage modules can be chosen
93
101
  # Each module will be called when storing results from inspections
94
102
  # Selecting a different storage module than local may require certain access rights
95
103
  STORAGE_LOCAL_ENABLED: bool = Field(default=True)
96
104
  STORAGE_BLOB_ENABLED: bool = Field(default=False)
97
- STORAGE_SLIMM_ENABLED: bool = Field(default=False)
98
105
 
99
106
  # Determines whether the MQTT publishing module should be enabled or not
100
107
  # The publishing module will attempt to connect to the MQTT broker configured in
@@ -110,7 +117,7 @@ class Settings(BaseSettings):
110
117
  # Enabling this requires certain resources available for OAuth2 authentication
111
118
  # Currently supported authentication is Azure AD
112
119
  # (https://github.com/Intility/fastapi-azure-auth)
113
- AUTHENTICATION_ENABLED: bool = Field(default=False)
120
+ AUTHENTICATION_ENABLED: bool = Field(default=True)
114
121
 
115
122
  # Tenant ID for the Azure tenant with your Azure Active Directory application
116
123
  AZURE_TENANT_ID: str = Field(default="3aa4a235-b6e2-48d5-9195-7fcf05b459b0")
@@ -140,46 +147,28 @@ class Settings(BaseSettings):
140
147
  # Keyvault name
141
148
  KEYVAULT_NAME: str = Field(default="IsarDevKv")
142
149
 
150
+ # Determines whether inspections are uploaded asynchronously or get_inspections in robotinterface
151
+ UPLOAD_INSPECTIONS_ASYNC: bool = Field(default=False)
152
+
143
153
  # URL to storage account for Azure Blob Storage
144
- BLOB_STORAGE_ACCOUNT_URL: str = Field(
145
- default="https://eqrobotdevstorage.blob.core.windows.net"
146
- )
154
+ BLOB_STORAGE_ACCOUNT: str = Field(default="")
147
155
 
148
156
  # Name of blob container in Azure Blob Storage [slimm test]
149
157
  BLOB_CONTAINER: str = Field(default="test")
150
158
 
151
- # Client ID for SLIMM App Registration
152
- SLIMM_CLIENT_ID: str = Field(default="c630ca4d-d8d6-45ab-8cc6-68a363d0de9e")
153
-
154
- # Scope for access to SLIMM Ingestion API
155
- SLIMM_APP_SCOPE: str = Field(default=".default")
156
-
157
- # URL for SLIMM endpoint
158
- SLIMM_API_URL: str = Field(
159
- default="https://scinspectioningestapitest.azurewebsites.net/Ingest"
160
- )
161
-
162
- # Whether the results should be copied directly into the SLIMM datalake or only the
163
- # metadata
164
- COPY_FILES_TO_SLIMM_DATALAKE: bool = Field(default=False)
159
+ PERSISTENT_STORAGE_CONNECTION_STRING: str = Field(default="")
165
160
 
166
161
  # The configuration of this section is tightly coupled with the metadata that is
167
162
  # submitted with the results once they have been uploaded.
168
163
 
169
164
  # Four digit code indicating facility
170
- PLANT_CODE: str = Field(default="1320")
165
+ PLANT_CODE: str = Field(default="1210")
171
166
 
172
167
  # Name of the facility the robot is operating in
173
- PLANT_NAME: str = Field(default="Kårstø")
168
+ PLANT_NAME: str = Field(default="Huldra")
174
169
 
175
170
  # Shortname of the facility the robot is operating in
176
- PLANT_SHORT_NAME: str = Field(default="KAA")
177
-
178
- # Country the robot is operating in
179
- COUNTRY: str = Field(default="Norway")
180
-
181
- # Type of robot ISAR is monitoring
182
- ROBOT_TYPE: str = Field(default="robot")
171
+ PLANT_SHORT_NAME: str = Field(default="HUA")
183
172
 
184
173
  # Name of robot
185
174
  ROBOT_NAME: str = Field(default="Placebot")
@@ -190,25 +179,8 @@ class Settings(BaseSettings):
190
179
  # Serial number of the robot ISAR is connected to
191
180
  SERIAL_NUMBER: str = Field(default="0001")
192
181
 
193
- # Endpoints to reach video streams for the robot
194
- VIDEO_STREAMS: List[VideoStream] = Field(
195
- default=[
196
- VideoStream(
197
- name="Front camera",
198
- url="http://localhost:5000/videostream/front",
199
- type="turtlebot",
200
- ),
201
- VideoStream(
202
- name="Rear camera",
203
- url="http://localhost:5000/videostream/rear",
204
- type="turtlebot",
205
- ),
206
- ]
207
- )
208
-
209
- # Data scheme the robot should adhere to
210
- # Options [DS0001]
211
- DATA_SCHEME: str = Field(default="DS0001")
182
+ # Info about robot documentation
183
+ DOCUMENTATION: List[DocumentInfo] = Field(default=[])
212
184
 
213
185
  # Coordinate reference system of facility
214
186
  COORDINATE_REFERENCE_SYSTEM: str = Field(default="EQUINOR:4100001")
@@ -220,9 +192,6 @@ class Settings(BaseSettings):
220
192
  # Options [quaternion]
221
193
  MEDIA_ORIENTATION_REFERENCE_SYSTEM: str = Field(default="quaternion")
222
194
 
223
- # Contractor who is responsible for robot missions
224
- CONTRACTOR: str = Field(default="equinor")
225
-
226
195
  # Timezone
227
196
  TIMEZONE: str = Field(default="UTC+00:00")
228
197
 
@@ -232,15 +201,29 @@ class Settings(BaseSettings):
232
201
  # List of MQTT Topics
233
202
  TOPIC_ISAR_STATUS: str = Field(default="status", validate_default=True)
234
203
  TOPIC_ISAR_MISSION: str = Field(default="mission", validate_default=True)
204
+ TOPIC_ISAR_MISSION_ABORTED: str = Field(
205
+ default="aborted_mission", validate_default=True
206
+ )
235
207
  TOPIC_ISAR_TASK: str = Field(default="task", validate_default=True)
236
- TOPIC_ISAR_STEP: str = Field(default="step", validate_default=True)
237
208
  TOPIC_ISAR_INSPECTION_RESULT: str = Field(
238
209
  default="inspection_result", validate_default=True
239
210
  )
211
+ TOPIC_ISAR_INSPECTION_VALUE: str = Field(
212
+ default="inspection_value", validate_default=True
213
+ )
240
214
  TOPIC_ISAR_ROBOT_INFO: str = Field(default="robot_info", validate_default=True)
241
215
  TOPIC_ISAR_ROBOT_HEARTBEAT: str = Field(
242
216
  default="robot_heartbeat", validate_default=True
243
217
  )
218
+ TOPIC_ISAR_STARTUP: str = Field(default="startup", validate_default=True)
219
+ TOPIC_ISAR_INTERVENTION_NEEDED: str = Field(
220
+ default="intervention_needed", validate_default=True
221
+ )
222
+
223
+ # List of MQTT Topics Expiry
224
+ MQTT_ROBOT_HEARTBEAT_EXPIRY: int = Field(default=5)
225
+ MQTT_TELEMETRY_EXPIRY: int = Field(default=10)
226
+ MQTT_MISSION_AND_TASK_EXPIRY: int = Field(default=86400)
244
227
 
245
228
  # Logging
246
229
 
@@ -250,47 +233,29 @@ class Settings(BaseSettings):
250
233
  # Each handler will be called when logging
251
234
  # Selecting a different log handler than local may require certain access rights:
252
235
  # - The Azure AI logger requires the 'APPLICATIONINSIGHTS_CONNECTION_STRING' to be set as an environment variable.
236
+ # In general logging is configured in logging.conf
253
237
  LOG_HANDLER_LOCAL_ENABLED: bool = Field(default=True)
254
238
  LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED: bool = Field(default=False)
255
239
 
256
- # Log levels
257
- API_LOG_LEVEL: str = Field(default="INFO")
258
- MAIN_LOG_LEVEL: str = Field(default="INFO")
259
- MQTT_LOG_LEVEL: str = Field(default="INFO")
260
- STATE_MACHINE_LOG_LEVEL: str = Field(default="INFO")
261
- UPLOADER_LOG_LEVEL: str = Field(default="INFO")
262
- CONSOLE_LOG_LEVEL: str = Field(default="INFO")
263
- URLLIB3_LOG_LEVEL: str = Field(default="WARNING")
264
- UVICORN_LOG_LEVEL: str = Field(default="WARNING")
265
- AZURE_LOG_LEVEL: str = Field(default="WARNING")
240
+ DEBUG_LOG_FORMATTER: bool = Field(default=False)
266
241
 
267
- LOG_LEVELS: dict = Field(default={})
242
+ # You can set logger levels from environment variables ending with _LOG_LEVEL
243
+ # For example, MQTT_LOG_LEVEL="DEBUG" will set the logger MQTT_LOGGER to DEBUG level
244
+ # Handeled in log.py and only work for loggers defined in logging.conf
268
245
 
269
246
  REQUIRED_ROLE: str = Field(default="Mission.Control")
270
247
 
271
- @field_validator("LOG_LEVELS")
272
- @classmethod
273
- def set_log_levels(cls, v: Any, info: ValidationInfo) -> dict:
274
- return {
275
- "api": info.data["API_LOG_LEVEL"],
276
- "main": info.data["MAIN_LOG_LEVEL"],
277
- "mqtt": info.data["MQTT_LOG_LEVEL"],
278
- "state_machine": info.data["STATE_MACHINE_LOG_LEVEL"],
279
- "uploader": info.data["UPLOADER_LOG_LEVEL"],
280
- "console": info.data["CONSOLE_LOG_LEVEL"],
281
- "urllib3": info.data["URLLIB3_LOG_LEVEL"],
282
- "uvicorn": info.data["UVICORN_LOG_LEVEL"],
283
- "azure": info.data["AZURE_LOG_LEVEL"],
284
- }
285
-
286
248
  @field_validator(
287
249
  "TOPIC_ISAR_STATUS",
288
250
  "TOPIC_ISAR_MISSION",
289
251
  "TOPIC_ISAR_TASK",
290
- "TOPIC_ISAR_STEP",
291
252
  "TOPIC_ISAR_ROBOT_INFO",
292
253
  "TOPIC_ISAR_ROBOT_HEARTBEAT",
293
254
  "TOPIC_ISAR_INSPECTION_RESULT",
255
+ "TOPIC_ISAR_INSPECTION_VALUE",
256
+ "TOPIC_ISAR_STARTUP",
257
+ "TOPIC_ISAR_INTERVENTION_NEEDED",
258
+ "TOPIC_ISAR_MISSION_ABORTED",
294
259
  )
295
260
  @classmethod
296
261
  def prefix_isar_topics(cls, v: Any, info: ValidationInfo):
@@ -304,7 +269,12 @@ class Settings(BaseSettings):
304
269
  )
305
270
 
306
271
 
307
- load_dotenv()
272
+ env = os.environ.get("ISAR_ENV")
273
+
274
+ if env == "test":
275
+ load_dotenv(".env.test", override=True)
276
+ else:
277
+ load_dotenv()
308
278
  settings = Settings()
309
279
 
310
280
 
@@ -324,17 +294,12 @@ class RobotSettings(BaseSettings):
324
294
 
325
295
  # ISAR steps the robot is capable of performing
326
296
  # This should be set in the robot package settings.env file
327
- CAPABILITIES: List[str] = Field(default=["drive_to_pose", "take_image"])
297
+ CAPABILITIES: List[str] = Field(default=["take_image"])
328
298
 
329
299
  # Model of the robot which ISAR is connected to
330
300
  # This should be set in the robot package settings.env file
331
301
  ROBOT_MODEL: RobotModel = Field(default=RobotModel.Robot) # type: ignore
332
302
 
333
- # Valid arm poses that the robot may utilize
334
- # This should be set in the robot package settings.env file
335
- # Note that if the robot does not support moving an arm this will be None and
336
- # the functionality will be unavailable
337
- VALID_ARM_POSES: Optional[List[str]] = Field(default=None)
338
303
  model_config = SettingsConfigDict(
339
304
  env_file_encoding="utf-8", case_sensitive=True, extra="ignore"
340
305
  )
@@ -0,0 +1,123 @@
1
+ import logging
2
+ import time
3
+ from copy import deepcopy
4
+ from dataclasses import dataclass
5
+ from threading import Event as ThreadEvent
6
+ from typing import TYPE_CHECKING, Callable, Generic, List, Optional, TypeVar
7
+
8
+ from transitions import State
9
+
10
+ from isar.config.settings import settings
11
+ from isar.models.events import Event
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ @dataclass
17
+ class EventHandlerMapping(Generic[T]):
18
+ name: str
19
+ event: Event[T]
20
+ handler: Callable[[Event[T]], Optional[Callable]]
21
+
22
+
23
+ @dataclass
24
+ class TimeoutHandlerMapping:
25
+ name: str
26
+ timeout_in_seconds: float
27
+ handler: Callable[[], Optional[Callable]]
28
+
29
+
30
+ if TYPE_CHECKING:
31
+ from isar.state_machine.state_machine import StateMachine
32
+
33
+
34
+ class EventHandlerBase(State):
35
+ def __init__(
36
+ self,
37
+ state_machine: "StateMachine",
38
+ state_name: str,
39
+ event_handler_mappings: List[EventHandlerMapping],
40
+ timers: List[TimeoutHandlerMapping] = [],
41
+ on_entry: Optional[Callable[[], None]] = None,
42
+ on_transition: Optional[Callable[[], None]] = None,
43
+ ) -> None:
44
+
45
+ super().__init__(name=state_name, on_enter=self.start)
46
+ self.state_machine: "StateMachine" = state_machine
47
+ self.logger = logging.getLogger("state_machine")
48
+ self.events = state_machine.events
49
+ self.signal_state_machine_to_stop: ThreadEvent = (
50
+ state_machine.signal_state_machine_to_stop
51
+ )
52
+ self.event_handler_mappings = event_handler_mappings
53
+ self.state_name: str = state_name
54
+ self.timers = timers
55
+ self.on_entry = on_entry
56
+ self.on_transition = on_transition
57
+
58
+ def start(self) -> None:
59
+ self.state_machine.update_state()
60
+ if self.on_entry:
61
+ self.on_entry()
62
+ self._run()
63
+
64
+ def stop(self) -> None:
65
+ return
66
+
67
+ def get_event_handler_by_name(
68
+ self, event_handler_name: str
69
+ ) -> Optional[EventHandlerMapping]:
70
+ filtered_handlers = list(
71
+ filter(
72
+ lambda mapping: mapping.name == event_handler_name,
73
+ self.event_handler_mappings,
74
+ )
75
+ )
76
+ return filtered_handlers[0] if len(filtered_handlers) > 0 else None
77
+
78
+ def get_event_timer_by_name(
79
+ self, event_timer_name: str
80
+ ) -> Optional[TimeoutHandlerMapping]:
81
+ filtered_timers = list(
82
+ filter(
83
+ lambda mapping: mapping.name == event_timer_name,
84
+ self.timers,
85
+ )
86
+ )
87
+ return filtered_timers[0] if len(filtered_timers) > 0 else None
88
+
89
+ def _run(self) -> None:
90
+ should_exit_state: bool = False
91
+ timers = deepcopy(self.timers)
92
+ entered_time = time.time()
93
+ while True:
94
+ if self.signal_state_machine_to_stop.is_set():
95
+ self.logger.info(
96
+ "Stopping state machine from %s state", self.state_name
97
+ )
98
+ break
99
+
100
+ for timer in timers:
101
+ if time.time() - entered_time > timer.timeout_in_seconds:
102
+ transition_func = timer.handler()
103
+ timers.remove(timer)
104
+ if transition_func is not None:
105
+ transition_func()
106
+ should_exit_state = True
107
+ break
108
+
109
+ if should_exit_state:
110
+ break
111
+
112
+ for handler_mapping in self.event_handler_mappings:
113
+ transition_func = handler_mapping.handler(handler_mapping.event)
114
+ if transition_func is not None:
115
+ transition_func()
116
+ should_exit_state = True
117
+ break
118
+
119
+ if should_exit_state:
120
+ break
121
+ time.sleep(settings.FSM_SLEEP_TIME)
122
+ if self.on_transition:
123
+ self.on_transition()
@@ -1,23 +1,19 @@
1
+ import json
1
2
  import logging
2
3
  from pathlib import Path
3
4
 
4
- from alitra import Frame
5
- from injector import inject
6
-
7
5
  from isar.config.settings import settings
8
6
  from isar.mission_planner.mission_planner_interface import (
9
7
  MissionNotFoundError,
10
8
  MissionPlannerError,
11
9
  MissionPlannerInterface,
12
10
  )
13
- from isar.services.readers.base_reader import BaseReader, BaseReaderError
14
11
  from robot_interface.models.mission.mission import Mission
15
12
 
16
13
  logger = logging.getLogger("api")
17
14
 
18
15
 
19
16
  class LocalPlanner(MissionPlannerInterface):
20
- @inject
21
17
  def __init__(self):
22
18
  self.predefined_mission_folder = Path(settings.PREDEFINED_MISSIONS_FOLDER)
23
19
 
@@ -37,15 +33,10 @@ class LocalPlanner(MissionPlannerInterface):
37
33
 
38
34
  @staticmethod
39
35
  def read_mission_from_file(mission_path: Path) -> Mission:
40
- mission_dict: dict = BaseReader.read_json(location=mission_path)
41
- mission: Mission = BaseReader.dict_to_dataclass(
42
- dataclass_dict=mission_dict,
43
- target_dataclass=Mission,
44
- cast_config=[Frame],
45
- strict_config=True,
46
- )
36
+ with open(mission_path) as json_file:
37
+ mission_dict = json.load(json_file)
47
38
 
48
- return mission
39
+ return Mission(**mission_dict)
49
40
 
50
41
  def get_predefined_missions(self) -> dict:
51
42
  missions: dict = {}
@@ -54,13 +45,8 @@ class LocalPlanner(MissionPlannerInterface):
54
45
  for file in json_files:
55
46
  mission_name = file.stem
56
47
  path_to_file = self.predefined_mission_folder.joinpath(file.name)
57
- try:
58
- mission: Mission = self.read_mission_from_file(path_to_file)
59
- except BaseReaderError as e:
60
- logger.warning(
61
- f"Failed to read predefined mission {path_to_file} \n {e}"
62
- )
63
- continue
48
+
49
+ mission: Mission = self.read_mission_from_file(path_to_file)
64
50
  if mission.id in invalid_mission_ids:
65
51
  logger.warning(
66
52
  f"Duplicate mission id {mission.id} : {path_to_file.as_posix()}"
@@ -9,7 +9,7 @@ class MissionPlannerInterface(metaclass=ABCMeta):
9
9
  """
10
10
  Parameters
11
11
  ----------
12
- mission_id : int
12
+ mission_id : str
13
13
 
14
14
  Returns
15
15
  -------