isar 1.32.3__tar.gz → 1.33.1__tar.gz

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.

Potentially problematic release.


This version of isar might be problematic. Click here for more details.

Files changed (217) hide show
  1. {isar-1.32.3 → isar-1.33.1}/PKG-INFO +1 -1
  2. isar-1.33.1/docs/full_state_machine_diagram.png +0 -0
  3. isar-1.33.1/docs/mission_state_machine_diagram.png +0 -0
  4. isar-1.33.1/docs/robot_status_state_machine_diagram.png +0 -0
  5. {isar-1.32.3 → isar-1.33.1}/docs/update_state_diagram.py +53 -3
  6. {isar-1.32.3 → isar-1.33.1}/src/isar/apis/models/models.py +6 -0
  7. {isar-1.32.3 → isar-1.33.1}/src/isar/apis/schedule/scheduling_controller.py +21 -52
  8. {isar-1.32.3 → isar-1.33.1}/src/isar/config/open_telemetry.py +52 -12
  9. {isar-1.32.3 → isar-1.33.1}/src/isar/config/settings.py +17 -3
  10. {isar-1.32.3 → isar-1.33.1}/src/isar/eventhandlers/eventhandler.py +22 -0
  11. {isar-1.32.3 → isar-1.33.1}/src/isar/models/events.py +56 -27
  12. {isar-1.32.3 → isar-1.33.1}/src/isar/robot/robot_status.py +3 -0
  13. {isar-1.32.3 → isar-1.33.1}/src/isar/services/utilities/scheduling_utilities.py +52 -21
  14. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/state_machine.py +39 -1
  15. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/await_next_mission.py +3 -1
  16. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/home.py +3 -1
  17. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/monitor.py +22 -0
  18. isar-1.33.1/src/isar/state_machine/states/recharging.py +44 -0
  19. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/returning_home.py +15 -1
  20. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/robot_standing_still.py +3 -1
  21. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/stopping.py +33 -0
  22. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states_enum.py +1 -0
  23. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/pause.py +1 -1
  24. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/resume.py +1 -1
  25. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/start_mission.py +11 -3
  26. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/stop.py +3 -30
  27. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/mission.py +0 -2
  28. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/return_home.py +11 -1
  29. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/robot_status.py +10 -0
  30. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/utils/common_event_handlers.py +16 -4
  31. {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/PKG-INFO +1 -1
  32. {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/SOURCES.txt +1 -1
  33. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/inspection/inspection.py +6 -15
  34. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/mission/status.py +1 -0
  35. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/robot_interface.py +27 -0
  36. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/telemetry/payloads.py +10 -0
  37. {isar-1.32.3 → isar-1.33.1}/tests/conftest.py +0 -2
  38. {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/scheduler/test_scheduler_router.py +7 -7
  39. {isar-1.32.3 → isar-1.33.1}/tests/isar/models/communication/test_events.py +3 -3
  40. {isar-1.32.3 → isar-1.33.1}/tests/isar/services/utilities/test_queue_utilities.py +2 -2
  41. {isar-1.32.3 → isar-1.33.1}/tests/isar/services/utilities/test_scheduling_utilities.py +65 -1
  42. {isar-1.32.3 → isar-1.33.1}/tests/isar/state_machine/test_state_machine.py +189 -1
  43. {isar-1.32.3 → isar-1.33.1}/tests/test_double/robot_interface.py +3 -0
  44. isar-1.32.3/docs/full_state_machine_diagram.png +0 -0
  45. isar-1.32.3/docs/mission_state_machine_diagram.png +0 -0
  46. isar-1.32.3/docs/robot_status_state_machine_diagram.png +0 -0
  47. isar-1.32.3/tests/test_double/mqtt_client.py +0 -10
  48. {isar-1.32.3 → isar-1.33.1}/.dockerignore +0 -0
  49. {isar-1.32.3 → isar-1.33.1}/.env.test +0 -0
  50. {isar-1.32.3 → isar-1.33.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  51. {isar-1.32.3 → isar-1.33.1}/.github/ISSUE_TEMPLATE/feature.md +0 -0
  52. {isar-1.32.3 → isar-1.33.1}/.github/ISSUE_TEMPLATE/improvement.md +0 -0
  53. {isar-1.32.3 → isar-1.33.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  54. {isar-1.32.3 → isar-1.33.1}/.github/release.yml +0 -0
  55. {isar-1.32.3 → isar-1.33.1}/.github/workflows/compile_requirements.yml +0 -0
  56. {isar-1.32.3 → isar-1.33.1}/.github/workflows/project_automations.yml +0 -0
  57. {isar-1.32.3 → isar-1.33.1}/.github/workflows/pythonpackage.yml +0 -0
  58. {isar-1.32.3 → isar-1.33.1}/.github/workflows/pythonpublish.yml +0 -0
  59. {isar-1.32.3 → isar-1.33.1}/.github/workflows/stale.yml +0 -0
  60. {isar-1.32.3 → isar-1.33.1}/.gitignore +0 -0
  61. {isar-1.32.3 → isar-1.33.1}/.pre-commit-config.yaml +0 -0
  62. {isar-1.32.3 → isar-1.33.1}/LICENSE +0 -0
  63. {isar-1.32.3 → isar-1.33.1}/README.md +0 -0
  64. {isar-1.32.3 → isar-1.33.1}/SECURITY.md +0 -0
  65. {isar-1.32.3 → isar-1.33.1}/docs/Makefile +0 -0
  66. {isar-1.32.3 → isar-1.33.1}/docs/make.bat +0 -0
  67. {isar-1.32.3 → isar-1.33.1}/docs/rst_processing.py +0 -0
  68. {isar-1.32.3 → isar-1.33.1}/docs/source/conf.py +0 -0
  69. {isar-1.32.3 → isar-1.33.1}/docs/source/index.rst +0 -0
  70. {isar-1.32.3 → isar-1.33.1}/docs/source/readme_link.md +0 -0
  71. {isar-1.32.3 → isar-1.33.1}/main.py +0 -0
  72. {isar-1.32.3 → isar-1.33.1}/pyproject.toml +0 -0
  73. {isar-1.32.3 → isar-1.33.1}/radixconfig.yml +0 -0
  74. {isar-1.32.3 → isar-1.33.1}/requirements.txt +0 -0
  75. {isar-1.32.3 → isar-1.33.1}/setup.cfg +0 -0
  76. {isar-1.32.3 → isar-1.33.1}/src/isar/__init__.py +0 -0
  77. {isar-1.32.3 → isar-1.33.1}/src/isar/apis/__init__.py +0 -0
  78. {isar-1.32.3 → isar-1.33.1}/src/isar/apis/api.py +0 -0
  79. {isar-1.32.3 → isar-1.33.1}/src/isar/apis/models/__init__.py +0 -0
  80. {isar-1.32.3 → isar-1.33.1}/src/isar/apis/models/start_mission_definition.py +0 -0
  81. {isar-1.32.3 → isar-1.33.1}/src/isar/apis/robot_control/robot_controller.py +0 -0
  82. {isar-1.32.3 → isar-1.33.1}/src/isar/apis/schedule/__init__.py +0 -0
  83. {isar-1.32.3 → isar-1.33.1}/src/isar/apis/security/__init__.py +0 -0
  84. {isar-1.32.3 → isar-1.33.1}/src/isar/apis/security/authentication.py +0 -0
  85. {isar-1.32.3 → isar-1.33.1}/src/isar/config/__init__.py +0 -0
  86. {isar-1.32.3 → isar-1.33.1}/src/isar/config/certs/ca-cert.pem +0 -0
  87. {isar-1.32.3 → isar-1.33.1}/src/isar/config/configuration_error.py +0 -0
  88. {isar-1.32.3 → isar-1.33.1}/src/isar/config/keyvault/__init__.py +0 -0
  89. {isar-1.32.3 → isar-1.33.1}/src/isar/config/keyvault/keyvault_error.py +0 -0
  90. {isar-1.32.3 → isar-1.33.1}/src/isar/config/keyvault/keyvault_service.py +0 -0
  91. {isar-1.32.3 → isar-1.33.1}/src/isar/config/log.py +0 -0
  92. {isar-1.32.3 → isar-1.33.1}/src/isar/config/logging.conf +0 -0
  93. {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/JSP1_intermediate_deck.json +0 -0
  94. {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/JSP1_weather_deck.json +0 -0
  95. {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/default_map.json +0 -0
  96. {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/klab_b.json +0 -0
  97. {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/klab_compressor.json +0 -0
  98. {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/klab_turtlebot.json +0 -0
  99. {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/turtleworld.json +0 -0
  100. {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_mission_definition/__init__.py +0 -0
  101. {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_mission_definition/default_exr.json +0 -0
  102. {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_mission_definition/default_mission.json +0 -0
  103. {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_mission_definition/default_turtlebot.json +0 -0
  104. {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_missions/__init__.py +0 -0
  105. {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_missions/default.json +0 -0
  106. {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_missions/default_turtlebot.json +0 -0
  107. {isar-1.32.3 → isar-1.33.1}/src/isar/mission_planner/__init__.py +0 -0
  108. {isar-1.32.3 → isar-1.33.1}/src/isar/mission_planner/local_planner.py +0 -0
  109. {isar-1.32.3 → isar-1.33.1}/src/isar/mission_planner/mission_planner_interface.py +0 -0
  110. {isar-1.32.3 → isar-1.33.1}/src/isar/mission_planner/sequential_task_selector.py +0 -0
  111. {isar-1.32.3 → isar-1.33.1}/src/isar/mission_planner/task_selector_interface.py +0 -0
  112. {isar-1.32.3 → isar-1.33.1}/src/isar/models/__init__.py +0 -0
  113. {isar-1.32.3 → isar-1.33.1}/src/isar/modules.py +0 -0
  114. {isar-1.32.3 → isar-1.33.1}/src/isar/robot/robot.py +0 -0
  115. {isar-1.32.3 → isar-1.33.1}/src/isar/robot/robot_start_mission.py +0 -0
  116. {isar-1.32.3 → isar-1.33.1}/src/isar/robot/robot_stop_mission.py +0 -0
  117. {isar-1.32.3 → isar-1.33.1}/src/isar/robot/robot_task_status.py +0 -0
  118. {isar-1.32.3 → isar-1.33.1}/src/isar/script.py +0 -0
  119. {isar-1.32.3 → isar-1.33.1}/src/isar/services/__init__.py +0 -0
  120. {isar-1.32.3 → isar-1.33.1}/src/isar/services/auth/__init__.py +0 -0
  121. {isar-1.32.3 → isar-1.33.1}/src/isar/services/auth/azure_credentials.py +0 -0
  122. {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/__init__.py +0 -0
  123. {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/mqtt/__init__.py +0 -0
  124. {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/mqtt/mqtt_client.py +0 -0
  125. {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +0 -0
  126. {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/mqtt/robot_info_publisher.py +0 -0
  127. {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/request_handler.py +0 -0
  128. {isar-1.32.3 → isar-1.33.1}/src/isar/services/utilities/__init__.py +0 -0
  129. {isar-1.32.3 → isar-1.33.1}/src/isar/services/utilities/robot_utilities.py +0 -0
  130. {isar-1.32.3 → isar-1.33.1}/src/isar/services/utilities/threaded_request.py +0 -0
  131. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/__init__.py +0 -0
  132. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/__init__.py +0 -0
  133. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/blocked_protective_stop.py +0 -0
  134. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/intervention_needed.py +0 -0
  135. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/offline.py +0 -0
  136. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/paused.py +0 -0
  137. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/unknown_status.py +0 -0
  138. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/fail_mission.py +0 -0
  139. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/finish_mission.py +0 -0
  140. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/return_home.py +0 -0
  141. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/robot_status.py +0 -0
  142. {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/utils.py +0 -0
  143. {isar-1.32.3 → isar-1.33.1}/src/isar/storage/__init__.py +0 -0
  144. {isar-1.32.3 → isar-1.33.1}/src/isar/storage/blob_storage.py +0 -0
  145. {isar-1.32.3 → isar-1.33.1}/src/isar/storage/local_storage.py +0 -0
  146. {isar-1.32.3 → isar-1.33.1}/src/isar/storage/storage_interface.py +0 -0
  147. {isar-1.32.3 → isar-1.33.1}/src/isar/storage/uploader.py +0 -0
  148. {isar-1.32.3 → isar-1.33.1}/src/isar/storage/utilities.py +0 -0
  149. {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/dependency_links.txt +0 -0
  150. {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/entry_points.txt +0 -0
  151. {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/requires.txt +0 -0
  152. {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/top_level.txt +0 -0
  153. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/__init__.py +0 -0
  154. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/__init__.py +0 -0
  155. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/exceptions/__init__.py +0 -0
  156. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/exceptions/robot_exceptions.py +0 -0
  157. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/initialize/__init__.py +0 -0
  158. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/inspection/__init__.py +0 -0
  159. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/mission/__init__.py +0 -0
  160. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/mission/mission.py +0 -0
  161. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/mission/task.py +0 -0
  162. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/robots/__init__.py +0 -0
  163. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/robots/battery_state.py +0 -0
  164. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/robots/media.py +0 -0
  165. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/robots/robot_model.py +0 -0
  166. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/telemetry/__init__.py +0 -0
  167. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/telemetry/mqtt_client.py +0 -0
  168. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/test_robot_interface.py +0 -0
  169. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/utilities/__init__.py +0 -0
  170. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/utilities/json_service.py +0 -0
  171. {isar-1.32.3 → isar-1.33.1}/src/robot_interface/utilities/uuid_string_factory.py +0 -0
  172. {isar-1.32.3 → isar-1.33.1}/tests/__init__.py +0 -0
  173. {isar-1.32.3 → isar-1.33.1}/tests/integration/__init__.py +0 -0
  174. {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/__init__.py +0 -0
  175. {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/config/__init__.py +0 -0
  176. {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/config/maps/__init__.py +0 -0
  177. {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/config/maps/turtleworld.json +0 -0
  178. {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/config/missions/__init__.py +0 -0
  179. {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/config/missions/default.json +0 -0
  180. {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/test_successful_mission.py +0 -0
  181. {isar-1.32.3 → isar-1.33.1}/tests/isar/__init__.py +0 -0
  182. {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/__init__.py +0 -0
  183. {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/models/__init__.py +0 -0
  184. {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/models/example_mission_definition.json +0 -0
  185. {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/models/test_start_mission_definition.py +0 -0
  186. {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/scheduler/__init__.py +0 -0
  187. {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/security/__init__.py +0 -0
  188. {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/security/test_authentication.py +0 -0
  189. {isar-1.32.3 → isar-1.33.1}/tests/isar/mission/__init__.py +0 -0
  190. {isar-1.32.3 → isar-1.33.1}/tests/isar/mission/test_mission.py +0 -0
  191. {isar-1.32.3 → isar-1.33.1}/tests/isar/models/__init__.py +0 -0
  192. {isar-1.32.3 → isar-1.33.1}/tests/isar/models/communication/__init__.py +0 -0
  193. {isar-1.32.3 → isar-1.33.1}/tests/isar/services/__init__.py +0 -0
  194. {isar-1.32.3 → isar-1.33.1}/tests/isar/services/readers/__init__.py +0 -0
  195. {isar-1.32.3 → isar-1.33.1}/tests/isar/services/readers/test_mission_reader.py +0 -0
  196. {isar-1.32.3 → isar-1.33.1}/tests/isar/services/service_connections/__init__.py +0 -0
  197. {isar-1.32.3 → isar-1.33.1}/tests/isar/services/service_connections/echo/__init__.py +0 -0
  198. {isar-1.32.3 → isar-1.33.1}/tests/isar/services/service_connections/test_base_request_handler.py +0 -0
  199. {isar-1.32.3 → isar-1.33.1}/tests/isar/services/utilities/__init__.py +0 -0
  200. {isar-1.32.3 → isar-1.33.1}/tests/isar/state_machine/__init__.py +0 -0
  201. {isar-1.32.3 → isar-1.33.1}/tests/isar/state_machine/states/__init__.py +0 -0
  202. {isar-1.32.3 → isar-1.33.1}/tests/isar/state_machine/states/test_monitor.py +0 -0
  203. {isar-1.32.3 → isar-1.33.1}/tests/isar/storage/test_blob_storage.py +0 -0
  204. {isar-1.32.3 → isar-1.33.1}/tests/isar/storage/test_uploader.py +0 -0
  205. {isar-1.32.3 → isar-1.33.1}/tests/test_data/test_map_config/test_map_config.json +0 -0
  206. {isar-1.32.3 → isar-1.33.1}/tests/test_data/test_mission_not_working.json +0 -0
  207. {isar-1.32.3 → isar-1.33.1}/tests/test_data/test_mission_working.json +0 -0
  208. {isar-1.32.3 → isar-1.33.1}/tests/test_data/test_mission_working_no_tasks.json +0 -0
  209. {isar-1.32.3 → isar-1.33.1}/tests/test_data/test_thermal_image_mission.json +0 -0
  210. {isar-1.32.3 → isar-1.33.1}/tests/test_double/__init__.py +0 -0
  211. {isar-1.32.3 → isar-1.33.1}/tests/test_double/blob_storage.py +0 -0
  212. {isar-1.32.3 → isar-1.33.1}/tests/test_double/mission_definition.py +0 -0
  213. {isar-1.32.3 → isar-1.33.1}/tests/test_double/pose.py +0 -0
  214. {isar-1.32.3 → isar-1.33.1}/tests/test_double/request.py +0 -0
  215. {isar-1.32.3 → isar-1.33.1}/tests/test_double/status.py +0 -0
  216. {isar-1.32.3 → isar-1.33.1}/tests/test_double/task.py +0 -0
  217. {isar-1.32.3 → isar-1.33.1}/tests/test_double/token.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: isar
3
- Version: 1.32.3
3
+ Version: 1.33.1
4
4
  Summary: Integration and Supervisory control of Autonomous Robots
5
5
  Author-email: Equinor ASA <fg_robots_dev@equinor.com>
6
6
  License: Eclipse Public License version 2.0
@@ -13,9 +13,59 @@ from isar.state_machine.transitions.return_home import get_return_home_transitio
13
13
  from isar.state_machine.transitions.robot_status import get_robot_status_transitions
14
14
 
15
15
 
16
+ def extract_function_name_from_callable(func):
17
+ try:
18
+ closure = getattr(func, "__closure__", None)
19
+ if closure:
20
+ for cell in closure:
21
+ obj = cell.cell_contents
22
+ if callable(obj):
23
+ return getattr(obj, "__name__", str(obj))
24
+
25
+ return getattr(func, "__name__", str(func))
26
+ except Exception:
27
+ return "unknown"
28
+
29
+
30
+ def embed_conditions_in_trigger(transitions: List[dict]) -> List[dict]:
31
+ updated = []
32
+ for t in transitions:
33
+ trigger = t.get("trigger", "")
34
+ conditions = t.get("conditions", [])
35
+
36
+ if not isinstance(conditions, list):
37
+ conditions = [conditions]
38
+
39
+ condition_names = []
40
+
41
+ for cond in conditions:
42
+ name = extract_function_name_from_callable(cond)
43
+ condition_names.append(name)
44
+
45
+ befores = t.get("before", [])
46
+ if not isinstance(befores, list):
47
+ befores = [befores]
48
+ before_names = [extract_function_name_from_callable(b) for b in befores]
49
+
50
+ label_parts = [trigger]
51
+ if condition_names:
52
+ label_parts.append(f"[{'; '.join(condition_names)}]")
53
+ if before_names:
54
+ label_parts.append(f"/ {'; '.join(before_names)}")
55
+
56
+ new_trigger = " ".join(label_parts)
57
+
58
+ t_copy = t.copy()
59
+ t_copy["trigger"] = new_trigger
60
+ updated.append(t_copy)
61
+
62
+ return updated
63
+
64
+
16
65
  def draw_diagram(states: List[State], transitions: List[dict], name: str):
66
+ transitions_with_conditions = embed_conditions_in_trigger(transitions)
17
67
  machine = GraphMachine(states=states, initial="unknown_status", queued=True)
18
- machine.add_transitions(transitions)
68
+ machine.add_transitions(transitions_with_conditions)
19
69
  gp = machine.get_combined_graph()
20
70
 
21
71
  state_machine_diagram_file = (
@@ -25,12 +75,12 @@ def draw_diagram(states: List[State], transitions: List[dict], name: str):
25
75
  if os.path.isfile(state_machine_diagram_file):
26
76
  os.remove(state_machine_diagram_file)
27
77
 
28
- gp.draw(state_machine_diagram_file, prog="dot")
78
+ gp.draw(str(state_machine_diagram_file), prog="dot", format="png")
29
79
 
30
80
 
31
81
  if __name__ == "__main__":
32
82
  injector: Injector = get_injector()
33
- state_machine: StateMachine = injector.get(StateMachine)
83
+ state_machine: StateMachine = injector.state_machine()
34
84
 
35
85
  mission_extended_transitions: List[dict] = []
36
86
  for transition in get_mission_transitions(state_machine):
@@ -26,6 +26,12 @@ class ControlMissionResponse(BaseModel):
26
26
  task_status: Optional[str]
27
27
 
28
28
 
29
+ class MissionStartResponse(BaseModel):
30
+ mission_id: Optional[str] = None
31
+ mission_started: bool
32
+ mission_not_started_reason: Optional[str] = None
33
+
34
+
29
35
  class RobotInfoResponse(BaseModel):
30
36
  robot_package: str
31
37
  isar_id: str
@@ -1,6 +1,5 @@
1
1
  import logging
2
2
  from http import HTTPStatus
3
- from threading import Lock
4
3
 
5
4
  from fastapi import Body, HTTPException, Path
6
5
 
@@ -29,7 +28,6 @@ class SchedulingController:
29
28
  ):
30
29
  self.scheduling_utilities: SchedulingUtilities = scheduling_utilities
31
30
  self.logger = logging.getLogger("api")
32
- self.start_mission_lock: Lock = Lock()
33
31
 
34
32
  def start_mission_by_id(
35
33
  self,
@@ -77,67 +75,38 @@ class SchedulingController:
77
75
  detail=error_message_no_mission_definition,
78
76
  )
79
77
 
80
- if not self.start_mission_lock.acquire(blocking=False):
81
- error_message_another_mission_starting: str = (
82
- "Conflict - Another mission is currently being started"
83
- )
84
- self.logger.warning(error_message_another_mission_starting)
85
- raise HTTPException(
86
- status_code=HTTPStatus.CONFLICT,
87
- detail=error_message_another_mission_starting,
88
- )
78
+ state: States = self.scheduling_utilities.get_state()
79
+ self.scheduling_utilities.verify_state_machine_ready_to_receive_mission(state)
89
80
 
90
81
  try:
91
- state: States = self.scheduling_utilities.get_state()
92
- self.scheduling_utilities.verify_state_machine_ready_to_receive_mission(
93
- state
82
+ mission: Mission = to_isar_mission(
83
+ start_mission_definition=mission_definition
94
84
  )
95
-
96
- try:
97
- mission: Mission = to_isar_mission(
98
- start_mission_definition=mission_definition
99
- )
100
- except MissionPlannerError as e:
101
- error_message = f"Bad Request - Cannot create ISAR mission: {e}"
102
- self.logger.warning(error_message)
103
- raise HTTPException(
104
- status_code=HTTPStatus.BAD_REQUEST,
105
- detail=error_message,
106
- )
107
-
108
- self.scheduling_utilities.verify_robot_capable_of_mission(
109
- mission=mission, robot_capabilities=robot_settings.CAPABILITIES
85
+ except MissionPlannerError as e:
86
+ error_message = f"Bad Request - Cannot create ISAR mission: {e}"
87
+ self.logger.warning(error_message)
88
+ raise HTTPException(
89
+ status_code=HTTPStatus.BAD_REQUEST,
90
+ detail=error_message,
110
91
  )
111
92
 
112
- self.logger.info("Starting mission: %s", mission.id)
113
- self.scheduling_utilities.start_mission(mission=mission)
114
- return self._api_response(mission)
93
+ self.scheduling_utilities.verify_robot_capable_of_mission(
94
+ mission=mission, robot_capabilities=robot_settings.CAPABILITIES
95
+ )
115
96
 
116
- finally:
117
- self.start_mission_lock.release()
97
+ self.logger.info("Starting mission: %s", mission.id)
98
+ self.scheduling_utilities.start_mission(mission=mission)
99
+ return self._api_response(mission)
118
100
 
119
101
  def return_home(self) -> None:
120
102
  self.logger.info("Received request to return home")
121
103
 
122
- if not self.start_mission_lock.acquire(blocking=False):
123
- error_message_another_mission_starting: str = (
124
- "Conflict - Another mission is currently being started"
125
- )
126
- self.logger.warning(error_message_another_mission_starting)
127
- raise HTTPException(
128
- status_code=HTTPStatus.CONFLICT,
129
- detail=error_message_another_mission_starting,
130
- )
131
-
132
- try:
133
- state: States = self.scheduling_utilities.get_state()
134
- self.scheduling_utilities.verify_state_machine_ready_to_receive_return_home_mission(
135
- state
136
- )
104
+ state: States = self.scheduling_utilities.get_state()
105
+ self.scheduling_utilities.verify_state_machine_ready_to_receive_return_home_mission(
106
+ state
107
+ )
137
108
 
138
- self.scheduling_utilities.return_home()
139
- finally:
140
- self.start_mission_lock.release()
109
+ self.scheduling_utilities.return_home()
141
110
 
142
111
  def pause_mission(self) -> ControlMissionResponse:
143
112
  self.logger.info("Received request to pause current mission")
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from urllib.parse import urljoin
2
3
 
3
4
  from azure.monitor.opentelemetry.exporter import (
4
5
  AzureMonitorLogExporter,
@@ -7,6 +8,12 @@ from azure.monitor.opentelemetry.exporter import (
7
8
  from fastapi import FastAPI
8
9
  from opentelemetry import trace
9
10
  from opentelemetry._logs import set_logger_provider
11
+ from opentelemetry.exporter.otlp.proto.http._log_exporter import (
12
+ OTLPLogExporter as OTLPHttpLogExporter,
13
+ )
14
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
15
+ OTLPSpanExporter as OTLPHttpSpanExporter,
16
+ )
10
17
  from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
11
18
  from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
12
19
  from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
@@ -17,22 +24,45 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor
17
24
  from isar.config.log import load_log_config
18
25
  from isar.config.settings import settings
19
26
 
27
+ logging.getLogger("opentelemetry.sdk").setLevel(logging.CRITICAL)
28
+
20
29
 
21
30
  def setup_open_telemetry(app: FastAPI) -> None:
22
- if not settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED:
23
- return
24
- trace_exporter, log_exporter = get_azure_monitor_exporters()
25
31
 
26
- service_name = settings.OPEN_TELEMETRY_SERVICE_NAME
32
+ service_name = settings.ROBOT_NAME
27
33
  resource = Resource.create({SERVICE_NAME: service_name})
28
34
 
29
35
  tracer_provider = TracerProvider(resource=resource)
30
- tracer_provider.add_span_processor(BatchSpanProcessor(trace_exporter))
31
- trace.set_tracer_provider(tracer_provider)
32
-
33
36
  log_provider = LoggerProvider(resource=resource)
37
+
38
+ if settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED:
39
+ print("[OTEL] Azure Monitor exporters enabled")
40
+ azure_monitor_trace_exporter, azure_monitor_log_exporter = (
41
+ get_azure_monitor_exporters()
42
+ )
43
+
44
+ tracer_provider.add_span_processor(
45
+ BatchSpanProcessor(azure_monitor_trace_exporter)
46
+ )
47
+
48
+ log_provider.add_log_record_processor(
49
+ BatchLogRecordProcessor(azure_monitor_log_exporter)
50
+ )
51
+
52
+ otlp_exporter_endpoint = settings.OPEN_TELEMETRY_OTLP_EXPORTER_ENDPOINT
53
+ if otlp_exporter_endpoint:
54
+ print(f"[OTEL] OTLP exporters enabled, endpoint={otlp_exporter_endpoint}")
55
+ otlp_trace_exporter, otlp_log_exporter = get_otlp_exporters(
56
+ otlp_exporter_endpoint
57
+ )
58
+ tracer_provider.add_span_processor(BatchSpanProcessor(otlp_trace_exporter))
59
+
60
+ log_provider.add_log_record_processor(
61
+ BatchLogRecordProcessor(otlp_log_exporter)
62
+ )
63
+
34
64
  set_logger_provider(log_provider)
35
- log_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))
65
+ trace.set_tracer_provider(tracer_provider)
36
66
 
37
67
  handler = LoggingHandler(logger_provider=log_provider)
38
68
  attach_loggers_for_open_telemetry(handler)
@@ -51,12 +81,22 @@ def attach_loggers_for_open_telemetry(handler: LoggingHandler):
51
81
  def get_azure_monitor_exporters() -> (
52
82
  tuple[AzureMonitorTraceExporter, AzureMonitorLogExporter]
53
83
  ):
54
- """
55
- If connection string is defined in environment variables, then use it to create Azure Monitor Exporters.
56
- Else use Azure Managed Identity to create Azure Monitor Exporters.
57
- """
58
84
  connection_string = settings.APPLICATIONINSIGHTS_CONNECTION_STRING
59
85
  trace_exporter = AzureMonitorTraceExporter(connection_string=connection_string)
60
86
  log_exporter = AzureMonitorLogExporter(connection_string=connection_string)
61
87
 
62
88
  return trace_exporter, log_exporter
89
+
90
+
91
+ def get_otlp_exporters(
92
+ endpoint: str,
93
+ ) -> tuple[OTLPHttpSpanExporter, OTLPHttpLogExporter]:
94
+ base = endpoint.rstrip("/") + "/"
95
+ trace_ep = urljoin(base, "v1/traces")
96
+ log_ep = urljoin(base, "v1/logs")
97
+
98
+ print("[OTEL] Using HTTP/Protobuf protocol for OpenTelemetry export")
99
+ print(f"[OTEL] traces → {trace_ep}")
100
+ print(f"[OTEL] logs → {log_ep}")
101
+
102
+ return OTLPHttpSpanExporter(endpoint=trace_ep), OTLPHttpLogExporter(endpoint=log_ep)
@@ -12,8 +12,9 @@ from robot_interface.telemetry.payloads import DocumentInfo
12
12
 
13
13
 
14
14
  class Settings(BaseSettings):
15
- # Name of the OpenTelemetry service
16
- OPEN_TELEMETRY_SERVICE_NAME: str = Field(default="isar")
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
+
17
18
  # Connection string for Azure Application Insights
18
19
  # This is optional and it will use managed identity if not set
19
20
  APPLICATIONINSIGHTS_CONNECTION_STRING: str = Field(default="")
@@ -29,7 +30,7 @@ class Settings(BaseSettings):
29
30
  REQUEST_TIMEOUT: int = Field(default=30)
30
31
 
31
32
  # Timeout in seconds for checking whether there is a message on a queue
32
- QUEUE_TIMEOUT: int = Field(default=10)
33
+ QUEUE_TIMEOUT: int = Field(default=3)
33
34
 
34
35
  # Sleep time for while loops in the finite state machine in seconds
35
36
  # The sleep is used to throttle the system on every iteration in the loop
@@ -80,6 +81,15 @@ class Settings(BaseSettings):
80
81
  ROBOT_API_STATUS_POLL_INTERVAL: float = Field(default=5)
81
82
  THREAD_CHECK_INTERVAL: float = Field(default=0.01)
82
83
 
84
+ # Determines the minimum battery level the robot must have to start a mission
85
+ # If it drops below this level it will recharge to the value set by
86
+ # ROBOT_BATTERY_RECHARGE_THRESHOLD before starting new missions
87
+ ROBOT_MISSION_BATTERY_START_THRESHOLD: float = Field(default=25.0)
88
+
89
+ # Determines the minimum battery threshold to consider the robot recharged
90
+ # and ready for more missions, after having run low on charge
91
+ ROBOT_BATTERY_RECHARGE_THRESHOLD: float = Field(default=80.0)
92
+
83
93
  # FastAPI host
84
94
  API_HOST_VIEWED_EXTERNALLY: str = Field(default="0.0.0.0")
85
95
 
@@ -215,6 +225,9 @@ class Settings(BaseSettings):
215
225
  # List of MQTT Topics
216
226
  TOPIC_ISAR_STATUS: str = Field(default="status", validate_default=True)
217
227
  TOPIC_ISAR_MISSION: str = Field(default="mission", validate_default=True)
228
+ TOPIC_ISAR_MISSION_ABORTED: str = Field(
229
+ default="aborted_mission", validate_default=True
230
+ )
218
231
  TOPIC_ISAR_TASK: str = Field(default="task", validate_default=True)
219
232
  TOPIC_ISAR_INSPECTION_RESULT: str = Field(
220
233
  default="inspection_result", validate_default=True
@@ -282,6 +295,7 @@ class Settings(BaseSettings):
282
295
  "TOPIC_ISAR_INSPECTION_VALUE",
283
296
  "TOPIC_ISAR_STARTUP",
284
297
  "TOPIC_ISAR_INTERVENTION_NEEDED",
298
+ "TOPIC_ISAR_MISSION_ABORTED",
285
299
  )
286
300
  @classmethod
287
301
  def prefix_isar_topics(cls, v: Any, info: ValidationInfo):
@@ -58,6 +58,28 @@ class EventHandlerBase(State):
58
58
  def stop(self) -> None:
59
59
  return
60
60
 
61
+ def get_event_handler_by_name(
62
+ self, event_handler_name: str
63
+ ) -> Optional[EventHandlerMapping]:
64
+ filtered_handlers = list(
65
+ filter(
66
+ lambda mapping: mapping.name == event_handler_name,
67
+ self.event_handler_mappings,
68
+ )
69
+ )
70
+ return filtered_handlers[0] if len(filtered_handlers) > 0 else None
71
+
72
+ def get_event_timer_by_name(
73
+ self, event_timer_name: str
74
+ ) -> Optional[TimeoutHandlerMapping]:
75
+ filtered_timers = list(
76
+ filter(
77
+ lambda mapping: mapping.name == event_timer_name,
78
+ self.timers,
79
+ )
80
+ )
81
+ return filtered_timers[0] if len(filtered_timers) > 0 else None
82
+
61
83
  def _run(self) -> None:
62
84
  should_exit_state: bool = False
63
85
  timers = deepcopy(self.timers)
@@ -4,7 +4,7 @@ from typing import Generic, Optional, TypeVar
4
4
 
5
5
  from transitions import State
6
6
 
7
- from isar.apis.models.models import ControlMissionResponse
7
+ from isar.apis.models.models import ControlMissionResponse, MissionStartResponse
8
8
  from isar.config.settings import settings
9
9
  from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
10
10
  from robot_interface.models.mission.mission import Mission
@@ -17,11 +17,19 @@ T2 = TypeVar("T2")
17
17
 
18
18
 
19
19
  class Event(Queue[T]):
20
- def __init__(self) -> None:
20
+ def __init__(self, name: str) -> None:
21
21
  super().__init__(maxsize=1)
22
+ self.name = name
22
23
 
23
- def trigger_event(self, data: T) -> None:
24
- self.put(data)
24
+ def trigger_event(self, data: T, timeout: int = None) -> None:
25
+ try:
26
+ # We always want a timeout when blocking for results, so that
27
+ # the thread will never get stuck waiting for a result
28
+ self.put(data, block=timeout is not None, timeout=timeout)
29
+ except Exception:
30
+ if timeout is not None:
31
+ raise EventTimeoutError
32
+ return None
25
33
 
26
34
  def consume_event(self, timeout: int = None) -> Optional[T]:
27
35
  try:
@@ -74,46 +82,67 @@ class APIEvent(Generic[T1, T2]):
74
82
  api to state machine while the response is from state machine to api.
75
83
  """
76
84
 
77
- def __init__(self):
78
- self.request: Event[T1] = Event()
79
- self.response: Event[T2] = Event()
85
+ def __init__(self, name: str):
86
+ self.request: Event[T1] = Event("api-" + name + "-request")
87
+ self.response: Event[T2] = Event("api-" + name + "-request")
80
88
 
81
89
 
82
90
  class APIRequests:
83
91
  def __init__(self) -> None:
84
- self.start_mission: APIEvent[Mission, bool] = APIEvent()
85
- self.stop_mission: APIEvent[str, ControlMissionResponse] = APIEvent()
86
- self.pause_mission: APIEvent[bool, ControlMissionResponse] = APIEvent()
87
- self.resume_mission: APIEvent[bool, ControlMissionResponse] = APIEvent()
88
- self.return_home: APIEvent[bool, bool] = APIEvent()
89
- self.release_intervention_needed: APIEvent[bool, bool] = APIEvent()
92
+ self.start_mission: APIEvent[Mission, MissionStartResponse] = APIEvent(
93
+ "start_mission"
94
+ )
95
+ self.stop_mission: APIEvent[str, ControlMissionResponse] = APIEvent(
96
+ "stop_mission"
97
+ )
98
+ self.pause_mission: APIEvent[bool, ControlMissionResponse] = APIEvent(
99
+ "pause_mission"
100
+ )
101
+ self.resume_mission: APIEvent[bool, ControlMissionResponse] = APIEvent(
102
+ "resume_mission"
103
+ )
104
+ self.return_home: APIEvent[bool, bool] = APIEvent("return_home")
105
+ self.release_intervention_needed: APIEvent[bool, bool] = APIEvent(
106
+ "release_intervention_needed"
107
+ )
90
108
 
91
109
 
92
110
  class StateMachineEvents:
93
111
  def __init__(self) -> None:
94
- self.start_mission: Event[Mission] = Event()
95
- self.stop_mission: Event[bool] = Event()
96
- self.pause_mission: Event[bool] = Event()
97
- self.task_status_request: Event[str] = Event()
112
+ self.start_mission: Event[Mission] = Event("start_mission")
113
+ self.stop_mission: Event[bool] = Event("stop_mission")
114
+ self.pause_mission: Event[bool] = Event("pause_mission")
115
+ self.task_status_request: Event[str] = Event("task_status_request")
98
116
 
99
117
 
100
118
  class RobotServiceEvents:
101
119
  def __init__(self) -> None:
102
- self.task_status_updated: Event[TaskStatus] = Event()
103
- self.task_status_failed: Event[ErrorMessage] = Event()
104
- self.mission_started: Event[bool] = Event()
105
- self.mission_failed: Event[ErrorMessage] = Event()
106
- self.robot_status_changed: Event[bool] = Event()
107
- self.mission_failed_to_stop: Event[ErrorMessage] = Event()
108
- self.mission_successfully_stopped: Event[bool] = Event()
120
+ self.task_status_updated: Event[TaskStatus] = Event("task_status_updated")
121
+ self.task_status_failed: Event[ErrorMessage] = Event("task_status_failed")
122
+ self.mission_started: Event[bool] = Event("mission_started")
123
+ self.mission_failed: Event[ErrorMessage] = Event("mission_failed")
124
+ self.robot_status_changed: Event[bool] = Event("robot_status_changed")
125
+ self.mission_failed_to_stop: Event[ErrorMessage] = Event(
126
+ "mission_failed_to_stop"
127
+ )
128
+ self.mission_successfully_stopped: Event[bool] = Event(
129
+ "mission_successfully_stopped"
130
+ )
109
131
 
110
132
 
111
133
  class SharedState:
112
134
  def __init__(self) -> None:
113
- self.state: Event[State] = Event()
114
- self.robot_status: Event[RobotStatus] = Event()
115
- self.state_machine_current_task: Event[TASKS] = Event()
135
+ self.state: Event[State] = Event("state")
136
+ self.robot_status: Event[RobotStatus] = Event("robot_status")
137
+ self.state_machine_current_task: Event[TASKS] = Event(
138
+ "state_machine_current_task"
139
+ )
140
+ self.robot_battery_level: Event[float] = Event("robot_battery_level")
116
141
 
117
142
 
118
143
  class EventTimeoutError(Exception):
119
144
  pass
145
+
146
+
147
+ class EventConflictError(Exception):
148
+ pass
@@ -48,7 +48,10 @@ class RobotStatusThread(Thread):
48
48
  self.last_robot_status_poll_time = time.time()
49
49
 
50
50
  robot_status = self.robot.robot_status()
51
+ robot_battery_level = self.robot.get_battery_level()
52
+
51
53
  self.shared_state.robot_status.update(robot_status)
54
+ self.shared_state.robot_battery_level.update(robot_battery_level)
52
55
  except RobotException as e:
53
56
  self.logger.error(f"Failed to retrieve robot status: {e}")
54
57
  continue
@@ -16,6 +16,7 @@ from isar.mission_planner.mission_planner_interface import (
16
16
  from isar.models.events import (
17
17
  APIEvent,
18
18
  APIRequests,
19
+ EventConflictError,
19
20
  Events,
20
21
  EventTimeoutError,
21
22
  SharedState,
@@ -176,16 +177,28 @@ class SchedulingUtilities:
176
177
  If there is a timeout while communicating with the state machine
177
178
  """
178
179
  try:
179
- self._send_command(
180
+ mission_start_response = self._send_command(
180
181
  deepcopy(mission),
181
182
  self.api_events.start_mission,
182
183
  )
184
+ if not mission_start_response.mission_started:
185
+ self.logger.warning(
186
+ f"Mission failed to start - {mission_start_response.mission_not_started_reason}"
187
+ )
188
+ raise HTTPException(
189
+ status_code=HTTPStatus.CONFLICT,
190
+ detail=mission_start_response.mission_not_started_reason,
191
+ )
192
+ except EventConflictError:
193
+ error_message = "Previous mission request is still being processed"
194
+ self.logger.warning(error_message)
195
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
183
196
  except EventTimeoutError:
184
- error_message = "Internal Server Error - Failed to start mission in ISAR"
185
- self.logger.error(error_message)
186
- raise HTTPException(
187
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
197
+ error_message = (
198
+ "State machine has entered a state which cannot start a mission"
188
199
  )
200
+ self.logger.warning(error_message)
201
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
189
202
  self.logger.info("OK - Mission started in ISAR")
190
203
 
191
204
  def return_home(
@@ -203,14 +216,14 @@ class SchedulingUtilities:
203
216
  True,
204
217
  self.api_events.return_home,
205
218
  )
219
+ except EventConflictError:
220
+ error_message = "Previous return home request is still being processed"
221
+ self.logger.warning(error_message)
222
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
206
223
  except EventTimeoutError:
207
- error_message = (
208
- "Internal Server Error - Failed to start return home mission in ISAR"
209
- )
210
- self.logger.error(error_message)
211
- raise HTTPException(
212
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
213
- )
224
+ error_message = "State machine has entered a state which cannot start a return home mission"
225
+ self.logger.warning(error_message)
226
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
214
227
  self.logger.info("OK - Return home mission started in ISAR")
215
228
 
216
229
  def pause_mission(self) -> ControlMissionResponse:
@@ -225,12 +238,16 @@ class SchedulingUtilities:
225
238
  response = self._send_command(True, self.api_events.pause_mission)
226
239
  self.logger.info("OK - Mission successfully paused")
227
240
  return response
241
+ except EventConflictError:
242
+ error_message = "Previous pause mission request is still being processed"
243
+ self.logger.warning(error_message)
244
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
228
245
  except EventTimeoutError:
229
- error_message = "Internal Server Error - Failed to pause mission"
230
- self.logger.error(error_message)
231
- raise HTTPException(
232
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
246
+ error_message = (
247
+ "State machine has entered a state which cannot pause a mission"
233
248
  )
249
+ self.logger.warning(error_message)
250
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
234
251
 
235
252
  def resume_mission(self) -> ControlMissionResponse:
236
253
  """Resume mission
@@ -244,6 +261,10 @@ class SchedulingUtilities:
244
261
  response = self._send_command(True, self.api_events.resume_mission)
245
262
  self.logger.info("OK - Mission successfully resumed")
246
263
  return response
264
+ except EventConflictError:
265
+ error_message = "Previous resume mission request is still being processed"
266
+ self.logger.warning(error_message)
267
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
247
268
  except EventTimeoutError:
248
269
  error_message = "Internal Server Error - Failed to resume mission"
249
270
  self.logger.error(error_message)
@@ -281,12 +302,16 @@ class SchedulingUtilities:
281
302
  raise HTTPException(
282
303
  status_code=HTTPStatus.CONFLICT, detail=error_message
283
304
  )
305
+ except EventConflictError:
306
+ error_message = "Previous stop mission request is still being processed"
307
+ self.logger.warning(error_message)
308
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
284
309
  except EventTimeoutError:
285
- error_message = "Internal Server Error - Failed to stop mission"
286
- self.logger.error(error_message)
287
- raise HTTPException(
288
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
310
+ error_message = (
311
+ "State machine has entered a state which cannot stop a mission"
289
312
  )
313
+ self.logger.warning(error_message)
314
+ raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
290
315
  self.logger.info("OK - Mission successfully stopped")
291
316
  return stop_mission_response
292
317
 
@@ -311,11 +336,17 @@ class SchedulingUtilities:
311
336
  )
312
337
 
313
338
  def _send_command(self, input: T1, api_event: APIEvent[T1, T2]) -> T2:
314
- api_event.request.trigger_event(input)
339
+ if api_event.request.has_event() or api_event.response.has_event():
340
+ raise EventConflictError("API event has already been sent")
341
+
315
342
  try:
343
+ api_event.request.trigger_event(input, timeout=1)
316
344
  return api_event.response.consume_event(timeout=self.queue_timeout)
317
345
  except EventTimeoutError as e:
318
346
  self.logger.error("Queue timed out")
319
347
  api_event.request.clear_event()
320
348
  self.logger.error("No output received for command to state machine")
321
349
  raise e
350
+ finally:
351
+ api_event.request.clear_event()
352
+ api_event.response.clear_event()