isar 1.32.3__tar.gz → 1.33.0__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.0}/PKG-INFO +1 -1
  2. isar-1.33.0/docs/full_state_machine_diagram.png +0 -0
  3. isar-1.33.0/docs/mission_state_machine_diagram.png +0 -0
  4. isar-1.33.0/docs/robot_status_state_machine_diagram.png +0 -0
  5. {isar-1.32.3 → isar-1.33.0}/docs/update_state_diagram.py +53 -3
  6. {isar-1.32.3 → isar-1.33.0}/src/isar/apis/models/models.py +6 -0
  7. {isar-1.32.3 → isar-1.33.0}/src/isar/config/open_telemetry.py +52 -12
  8. {isar-1.32.3 → isar-1.33.0}/src/isar/config/settings.py +17 -3
  9. {isar-1.32.3 → isar-1.33.0}/src/isar/eventhandlers/eventhandler.py +22 -0
  10. {isar-1.32.3 → isar-1.33.0}/src/isar/models/events.py +14 -4
  11. {isar-1.32.3 → isar-1.33.0}/src/isar/robot/robot_status.py +3 -0
  12. {isar-1.32.3 → isar-1.33.0}/src/isar/services/utilities/scheduling_utilities.py +49 -21
  13. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/state_machine.py +38 -0
  14. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/await_next_mission.py +3 -1
  15. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/home.py +3 -1
  16. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/monitor.py +24 -0
  17. isar-1.33.0/src/isar/state_machine/states/recharging.py +44 -0
  18. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/returning_home.py +19 -2
  19. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/robot_standing_still.py +3 -1
  20. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states_enum.py +1 -0
  21. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/functions/start_mission.py +10 -2
  22. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/return_home.py +10 -0
  23. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/robot_status.py +10 -0
  24. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/utils/common_event_handlers.py +18 -3
  25. {isar-1.32.3 → isar-1.33.0}/src/isar.egg-info/PKG-INFO +1 -1
  26. {isar-1.32.3 → isar-1.33.0}/src/isar.egg-info/SOURCES.txt +1 -1
  27. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/inspection/inspection.py +6 -15
  28. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/mission/status.py +1 -0
  29. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/robot_interface.py +27 -0
  30. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/telemetry/payloads.py +10 -0
  31. {isar-1.32.3 → isar-1.33.0}/tests/conftest.py +0 -2
  32. {isar-1.32.3 → isar-1.33.0}/tests/isar/apis/scheduler/test_scheduler_router.py +7 -7
  33. {isar-1.32.3 → isar-1.33.0}/tests/isar/services/utilities/test_scheduling_utilities.py +64 -0
  34. {isar-1.32.3 → isar-1.33.0}/tests/isar/state_machine/test_state_machine.py +189 -1
  35. {isar-1.32.3 → isar-1.33.0}/tests/test_double/robot_interface.py +3 -0
  36. isar-1.32.3/docs/full_state_machine_diagram.png +0 -0
  37. isar-1.32.3/docs/mission_state_machine_diagram.png +0 -0
  38. isar-1.32.3/docs/robot_status_state_machine_diagram.png +0 -0
  39. isar-1.32.3/tests/test_double/mqtt_client.py +0 -10
  40. {isar-1.32.3 → isar-1.33.0}/.dockerignore +0 -0
  41. {isar-1.32.3 → isar-1.33.0}/.env.test +0 -0
  42. {isar-1.32.3 → isar-1.33.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  43. {isar-1.32.3 → isar-1.33.0}/.github/ISSUE_TEMPLATE/feature.md +0 -0
  44. {isar-1.32.3 → isar-1.33.0}/.github/ISSUE_TEMPLATE/improvement.md +0 -0
  45. {isar-1.32.3 → isar-1.33.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  46. {isar-1.32.3 → isar-1.33.0}/.github/release.yml +0 -0
  47. {isar-1.32.3 → isar-1.33.0}/.github/workflows/compile_requirements.yml +0 -0
  48. {isar-1.32.3 → isar-1.33.0}/.github/workflows/project_automations.yml +0 -0
  49. {isar-1.32.3 → isar-1.33.0}/.github/workflows/pythonpackage.yml +0 -0
  50. {isar-1.32.3 → isar-1.33.0}/.github/workflows/pythonpublish.yml +0 -0
  51. {isar-1.32.3 → isar-1.33.0}/.github/workflows/stale.yml +0 -0
  52. {isar-1.32.3 → isar-1.33.0}/.gitignore +0 -0
  53. {isar-1.32.3 → isar-1.33.0}/.pre-commit-config.yaml +0 -0
  54. {isar-1.32.3 → isar-1.33.0}/LICENSE +0 -0
  55. {isar-1.32.3 → isar-1.33.0}/README.md +0 -0
  56. {isar-1.32.3 → isar-1.33.0}/SECURITY.md +0 -0
  57. {isar-1.32.3 → isar-1.33.0}/docs/Makefile +0 -0
  58. {isar-1.32.3 → isar-1.33.0}/docs/make.bat +0 -0
  59. {isar-1.32.3 → isar-1.33.0}/docs/rst_processing.py +0 -0
  60. {isar-1.32.3 → isar-1.33.0}/docs/source/conf.py +0 -0
  61. {isar-1.32.3 → isar-1.33.0}/docs/source/index.rst +0 -0
  62. {isar-1.32.3 → isar-1.33.0}/docs/source/readme_link.md +0 -0
  63. {isar-1.32.3 → isar-1.33.0}/main.py +0 -0
  64. {isar-1.32.3 → isar-1.33.0}/pyproject.toml +0 -0
  65. {isar-1.32.3 → isar-1.33.0}/radixconfig.yml +0 -0
  66. {isar-1.32.3 → isar-1.33.0}/requirements.txt +0 -0
  67. {isar-1.32.3 → isar-1.33.0}/setup.cfg +0 -0
  68. {isar-1.32.3 → isar-1.33.0}/src/isar/__init__.py +0 -0
  69. {isar-1.32.3 → isar-1.33.0}/src/isar/apis/__init__.py +0 -0
  70. {isar-1.32.3 → isar-1.33.0}/src/isar/apis/api.py +0 -0
  71. {isar-1.32.3 → isar-1.33.0}/src/isar/apis/models/__init__.py +0 -0
  72. {isar-1.32.3 → isar-1.33.0}/src/isar/apis/models/start_mission_definition.py +0 -0
  73. {isar-1.32.3 → isar-1.33.0}/src/isar/apis/robot_control/robot_controller.py +0 -0
  74. {isar-1.32.3 → isar-1.33.0}/src/isar/apis/schedule/__init__.py +0 -0
  75. {isar-1.32.3 → isar-1.33.0}/src/isar/apis/schedule/scheduling_controller.py +0 -0
  76. {isar-1.32.3 → isar-1.33.0}/src/isar/apis/security/__init__.py +0 -0
  77. {isar-1.32.3 → isar-1.33.0}/src/isar/apis/security/authentication.py +0 -0
  78. {isar-1.32.3 → isar-1.33.0}/src/isar/config/__init__.py +0 -0
  79. {isar-1.32.3 → isar-1.33.0}/src/isar/config/certs/ca-cert.pem +0 -0
  80. {isar-1.32.3 → isar-1.33.0}/src/isar/config/configuration_error.py +0 -0
  81. {isar-1.32.3 → isar-1.33.0}/src/isar/config/keyvault/__init__.py +0 -0
  82. {isar-1.32.3 → isar-1.33.0}/src/isar/config/keyvault/keyvault_error.py +0 -0
  83. {isar-1.32.3 → isar-1.33.0}/src/isar/config/keyvault/keyvault_service.py +0 -0
  84. {isar-1.32.3 → isar-1.33.0}/src/isar/config/log.py +0 -0
  85. {isar-1.32.3 → isar-1.33.0}/src/isar/config/logging.conf +0 -0
  86. {isar-1.32.3 → isar-1.33.0}/src/isar/config/maps/JSP1_intermediate_deck.json +0 -0
  87. {isar-1.32.3 → isar-1.33.0}/src/isar/config/maps/JSP1_weather_deck.json +0 -0
  88. {isar-1.32.3 → isar-1.33.0}/src/isar/config/maps/default_map.json +0 -0
  89. {isar-1.32.3 → isar-1.33.0}/src/isar/config/maps/klab_b.json +0 -0
  90. {isar-1.32.3 → isar-1.33.0}/src/isar/config/maps/klab_compressor.json +0 -0
  91. {isar-1.32.3 → isar-1.33.0}/src/isar/config/maps/klab_turtlebot.json +0 -0
  92. {isar-1.32.3 → isar-1.33.0}/src/isar/config/maps/turtleworld.json +0 -0
  93. {isar-1.32.3 → isar-1.33.0}/src/isar/config/predefined_mission_definition/__init__.py +0 -0
  94. {isar-1.32.3 → isar-1.33.0}/src/isar/config/predefined_mission_definition/default_exr.json +0 -0
  95. {isar-1.32.3 → isar-1.33.0}/src/isar/config/predefined_mission_definition/default_mission.json +0 -0
  96. {isar-1.32.3 → isar-1.33.0}/src/isar/config/predefined_mission_definition/default_turtlebot.json +0 -0
  97. {isar-1.32.3 → isar-1.33.0}/src/isar/config/predefined_missions/__init__.py +0 -0
  98. {isar-1.32.3 → isar-1.33.0}/src/isar/config/predefined_missions/default.json +0 -0
  99. {isar-1.32.3 → isar-1.33.0}/src/isar/config/predefined_missions/default_turtlebot.json +0 -0
  100. {isar-1.32.3 → isar-1.33.0}/src/isar/mission_planner/__init__.py +0 -0
  101. {isar-1.32.3 → isar-1.33.0}/src/isar/mission_planner/local_planner.py +0 -0
  102. {isar-1.32.3 → isar-1.33.0}/src/isar/mission_planner/mission_planner_interface.py +0 -0
  103. {isar-1.32.3 → isar-1.33.0}/src/isar/mission_planner/sequential_task_selector.py +0 -0
  104. {isar-1.32.3 → isar-1.33.0}/src/isar/mission_planner/task_selector_interface.py +0 -0
  105. {isar-1.32.3 → isar-1.33.0}/src/isar/models/__init__.py +0 -0
  106. {isar-1.32.3 → isar-1.33.0}/src/isar/modules.py +0 -0
  107. {isar-1.32.3 → isar-1.33.0}/src/isar/robot/robot.py +0 -0
  108. {isar-1.32.3 → isar-1.33.0}/src/isar/robot/robot_start_mission.py +0 -0
  109. {isar-1.32.3 → isar-1.33.0}/src/isar/robot/robot_stop_mission.py +0 -0
  110. {isar-1.32.3 → isar-1.33.0}/src/isar/robot/robot_task_status.py +0 -0
  111. {isar-1.32.3 → isar-1.33.0}/src/isar/script.py +0 -0
  112. {isar-1.32.3 → isar-1.33.0}/src/isar/services/__init__.py +0 -0
  113. {isar-1.32.3 → isar-1.33.0}/src/isar/services/auth/__init__.py +0 -0
  114. {isar-1.32.3 → isar-1.33.0}/src/isar/services/auth/azure_credentials.py +0 -0
  115. {isar-1.32.3 → isar-1.33.0}/src/isar/services/service_connections/__init__.py +0 -0
  116. {isar-1.32.3 → isar-1.33.0}/src/isar/services/service_connections/mqtt/__init__.py +0 -0
  117. {isar-1.32.3 → isar-1.33.0}/src/isar/services/service_connections/mqtt/mqtt_client.py +0 -0
  118. {isar-1.32.3 → isar-1.33.0}/src/isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +0 -0
  119. {isar-1.32.3 → isar-1.33.0}/src/isar/services/service_connections/mqtt/robot_info_publisher.py +0 -0
  120. {isar-1.32.3 → isar-1.33.0}/src/isar/services/service_connections/request_handler.py +0 -0
  121. {isar-1.32.3 → isar-1.33.0}/src/isar/services/utilities/__init__.py +0 -0
  122. {isar-1.32.3 → isar-1.33.0}/src/isar/services/utilities/robot_utilities.py +0 -0
  123. {isar-1.32.3 → isar-1.33.0}/src/isar/services/utilities/threaded_request.py +0 -0
  124. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/__init__.py +0 -0
  125. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/__init__.py +0 -0
  126. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/blocked_protective_stop.py +0 -0
  127. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/intervention_needed.py +0 -0
  128. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/offline.py +0 -0
  129. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/paused.py +0 -0
  130. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/stopping.py +0 -0
  131. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/states/unknown_status.py +0 -0
  132. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/functions/fail_mission.py +0 -0
  133. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/functions/finish_mission.py +0 -0
  134. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/functions/pause.py +0 -0
  135. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/functions/resume.py +0 -0
  136. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/functions/return_home.py +0 -0
  137. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/functions/robot_status.py +0 -0
  138. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/functions/stop.py +0 -0
  139. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/functions/utils.py +0 -0
  140. {isar-1.32.3 → isar-1.33.0}/src/isar/state_machine/transitions/mission.py +0 -0
  141. {isar-1.32.3 → isar-1.33.0}/src/isar/storage/__init__.py +0 -0
  142. {isar-1.32.3 → isar-1.33.0}/src/isar/storage/blob_storage.py +0 -0
  143. {isar-1.32.3 → isar-1.33.0}/src/isar/storage/local_storage.py +0 -0
  144. {isar-1.32.3 → isar-1.33.0}/src/isar/storage/storage_interface.py +0 -0
  145. {isar-1.32.3 → isar-1.33.0}/src/isar/storage/uploader.py +0 -0
  146. {isar-1.32.3 → isar-1.33.0}/src/isar/storage/utilities.py +0 -0
  147. {isar-1.32.3 → isar-1.33.0}/src/isar.egg-info/dependency_links.txt +0 -0
  148. {isar-1.32.3 → isar-1.33.0}/src/isar.egg-info/entry_points.txt +0 -0
  149. {isar-1.32.3 → isar-1.33.0}/src/isar.egg-info/requires.txt +0 -0
  150. {isar-1.32.3 → isar-1.33.0}/src/isar.egg-info/top_level.txt +0 -0
  151. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/__init__.py +0 -0
  152. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/__init__.py +0 -0
  153. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/exceptions/__init__.py +0 -0
  154. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/exceptions/robot_exceptions.py +0 -0
  155. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/initialize/__init__.py +0 -0
  156. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/inspection/__init__.py +0 -0
  157. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/mission/__init__.py +0 -0
  158. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/mission/mission.py +0 -0
  159. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/mission/task.py +0 -0
  160. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/robots/__init__.py +0 -0
  161. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/robots/battery_state.py +0 -0
  162. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/robots/media.py +0 -0
  163. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/models/robots/robot_model.py +0 -0
  164. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/telemetry/__init__.py +0 -0
  165. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/telemetry/mqtt_client.py +0 -0
  166. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/test_robot_interface.py +0 -0
  167. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/utilities/__init__.py +0 -0
  168. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/utilities/json_service.py +0 -0
  169. {isar-1.32.3 → isar-1.33.0}/src/robot_interface/utilities/uuid_string_factory.py +0 -0
  170. {isar-1.32.3 → isar-1.33.0}/tests/__init__.py +0 -0
  171. {isar-1.32.3 → isar-1.33.0}/tests/integration/__init__.py +0 -0
  172. {isar-1.32.3 → isar-1.33.0}/tests/integration/turtlebot/__init__.py +0 -0
  173. {isar-1.32.3 → isar-1.33.0}/tests/integration/turtlebot/config/__init__.py +0 -0
  174. {isar-1.32.3 → isar-1.33.0}/tests/integration/turtlebot/config/maps/__init__.py +0 -0
  175. {isar-1.32.3 → isar-1.33.0}/tests/integration/turtlebot/config/maps/turtleworld.json +0 -0
  176. {isar-1.32.3 → isar-1.33.0}/tests/integration/turtlebot/config/missions/__init__.py +0 -0
  177. {isar-1.32.3 → isar-1.33.0}/tests/integration/turtlebot/config/missions/default.json +0 -0
  178. {isar-1.32.3 → isar-1.33.0}/tests/integration/turtlebot/test_successful_mission.py +0 -0
  179. {isar-1.32.3 → isar-1.33.0}/tests/isar/__init__.py +0 -0
  180. {isar-1.32.3 → isar-1.33.0}/tests/isar/apis/__init__.py +0 -0
  181. {isar-1.32.3 → isar-1.33.0}/tests/isar/apis/models/__init__.py +0 -0
  182. {isar-1.32.3 → isar-1.33.0}/tests/isar/apis/models/example_mission_definition.json +0 -0
  183. {isar-1.32.3 → isar-1.33.0}/tests/isar/apis/models/test_start_mission_definition.py +0 -0
  184. {isar-1.32.3 → isar-1.33.0}/tests/isar/apis/scheduler/__init__.py +0 -0
  185. {isar-1.32.3 → isar-1.33.0}/tests/isar/apis/security/__init__.py +0 -0
  186. {isar-1.32.3 → isar-1.33.0}/tests/isar/apis/security/test_authentication.py +0 -0
  187. {isar-1.32.3 → isar-1.33.0}/tests/isar/mission/__init__.py +0 -0
  188. {isar-1.32.3 → isar-1.33.0}/tests/isar/mission/test_mission.py +0 -0
  189. {isar-1.32.3 → isar-1.33.0}/tests/isar/models/__init__.py +0 -0
  190. {isar-1.32.3 → isar-1.33.0}/tests/isar/models/communication/__init__.py +0 -0
  191. {isar-1.32.3 → isar-1.33.0}/tests/isar/models/communication/test_events.py +0 -0
  192. {isar-1.32.3 → isar-1.33.0}/tests/isar/services/__init__.py +0 -0
  193. {isar-1.32.3 → isar-1.33.0}/tests/isar/services/readers/__init__.py +0 -0
  194. {isar-1.32.3 → isar-1.33.0}/tests/isar/services/readers/test_mission_reader.py +0 -0
  195. {isar-1.32.3 → isar-1.33.0}/tests/isar/services/service_connections/__init__.py +0 -0
  196. {isar-1.32.3 → isar-1.33.0}/tests/isar/services/service_connections/echo/__init__.py +0 -0
  197. {isar-1.32.3 → isar-1.33.0}/tests/isar/services/service_connections/test_base_request_handler.py +0 -0
  198. {isar-1.32.3 → isar-1.33.0}/tests/isar/services/utilities/__init__.py +0 -0
  199. {isar-1.32.3 → isar-1.33.0}/tests/isar/services/utilities/test_queue_utilities.py +0 -0
  200. {isar-1.32.3 → isar-1.33.0}/tests/isar/state_machine/__init__.py +0 -0
  201. {isar-1.32.3 → isar-1.33.0}/tests/isar/state_machine/states/__init__.py +0 -0
  202. {isar-1.32.3 → isar-1.33.0}/tests/isar/state_machine/states/test_monitor.py +0 -0
  203. {isar-1.32.3 → isar-1.33.0}/tests/isar/storage/test_blob_storage.py +0 -0
  204. {isar-1.32.3 → isar-1.33.0}/tests/isar/storage/test_uploader.py +0 -0
  205. {isar-1.32.3 → isar-1.33.0}/tests/test_data/test_map_config/test_map_config.json +0 -0
  206. {isar-1.32.3 → isar-1.33.0}/tests/test_data/test_mission_not_working.json +0 -0
  207. {isar-1.32.3 → isar-1.33.0}/tests/test_data/test_mission_working.json +0 -0
  208. {isar-1.32.3 → isar-1.33.0}/tests/test_data/test_mission_working_no_tasks.json +0 -0
  209. {isar-1.32.3 → isar-1.33.0}/tests/test_data/test_thermal_image_mission.json +0 -0
  210. {isar-1.32.3 → isar-1.33.0}/tests/test_double/__init__.py +0 -0
  211. {isar-1.32.3 → isar-1.33.0}/tests/test_double/blob_storage.py +0 -0
  212. {isar-1.32.3 → isar-1.33.0}/tests/test_double/mission_definition.py +0 -0
  213. {isar-1.32.3 → isar-1.33.0}/tests/test_double/pose.py +0 -0
  214. {isar-1.32.3 → isar-1.33.0}/tests/test_double/request.py +0 -0
  215. {isar-1.32.3 → isar-1.33.0}/tests/test_double/status.py +0 -0
  216. {isar-1.32.3 → isar-1.33.0}/tests/test_double/task.py +0 -0
  217. {isar-1.32.3 → isar-1.33.0}/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.0
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,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
@@ -20,8 +20,13 @@ class Event(Queue[T]):
20
20
  def __init__(self) -> None:
21
21
  super().__init__(maxsize=1)
22
22
 
23
- def trigger_event(self, data: T) -> None:
24
- self.put(data)
23
+ def trigger_event(self, data: T, timeout: int = None) -> None:
24
+ try:
25
+ self.put(data, timeout=timeout)
26
+ except Exception:
27
+ if timeout is not None:
28
+ raise EventTimeoutError
29
+ return None
25
30
 
26
31
  def consume_event(self, timeout: int = None) -> Optional[T]:
27
32
  try:
@@ -81,7 +86,7 @@ class APIEvent(Generic[T1, T2]):
81
86
 
82
87
  class APIRequests:
83
88
  def __init__(self) -> None:
84
- self.start_mission: APIEvent[Mission, bool] = APIEvent()
89
+ self.start_mission: APIEvent[Mission, MissionStartResponse] = APIEvent()
85
90
  self.stop_mission: APIEvent[str, ControlMissionResponse] = APIEvent()
86
91
  self.pause_mission: APIEvent[bool, ControlMissionResponse] = APIEvent()
87
92
  self.resume_mission: APIEvent[bool, ControlMissionResponse] = APIEvent()
@@ -113,7 +118,12 @@ class SharedState:
113
118
  self.state: Event[State] = Event()
114
119
  self.robot_status: Event[RobotStatus] = Event()
115
120
  self.state_machine_current_task: Event[TASKS] = Event()
121
+ self.robot_battery_level: Event[float] = Event()
116
122
 
117
123
 
118
124
  class EventTimeoutError(Exception):
119
125
  pass
126
+
127
+
128
+ class EventConflictError(Exception):
129
+ 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,8 +336,11 @@ 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")
@@ -22,6 +22,7 @@ from isar.state_machine.states.intervention_needed import InterventionNeeded
22
22
  from isar.state_machine.states.monitor import Monitor
23
23
  from isar.state_machine.states.offline import Offline
24
24
  from isar.state_machine.states.paused import Paused
25
+ from isar.state_machine.states.recharging import Recharging
25
26
  from isar.state_machine.states.returning_home import ReturningHome
26
27
  from isar.state_machine.states.robot_standing_still import RobotStandingStill
27
28
  from isar.state_machine.states.stopping import Stopping
@@ -43,6 +44,7 @@ from robot_interface.robot_interface import RobotInterface
43
44
  from robot_interface.telemetry.mqtt_client import MqttClientInterface
44
45
  from robot_interface.telemetry.payloads import (
45
46
  InterventionNeededPayload,
47
+ MissionAbortedPayload,
46
48
  MissionPayload,
47
49
  RobotStatusPayload,
48
50
  TaskPayload,
@@ -108,6 +110,7 @@ class StateMachine(object):
108
110
  # Status states
109
111
  self.offline_state: State = Offline(self)
110
112
  self.blocked_protective_stopping_state: State = BlockedProtectiveStop(self)
113
+ self.recharging_state: State = Recharging(self)
111
114
 
112
115
  # Error and special status states
113
116
  self.unknown_status_state: State = UnknownStatus(self)
@@ -124,6 +127,7 @@ class StateMachine(object):
124
127
  self.blocked_protective_stopping_state,
125
128
  self.unknown_status_state,
126
129
  self.intervention_needed_state,
130
+ self.recharging_state,
127
131
  ]
128
132
 
129
133
  self.machine = Machine(
@@ -189,6 +193,12 @@ class StateMachine(object):
189
193
  self.current_task = None
190
194
  self.send_task_status()
191
195
 
196
+ def battery_level_is_above_mission_start_threshold(self):
197
+ return (
198
+ not self.shared_state.robot_battery_level.check()
199
+ < settings.ROBOT_MISSION_BATTERY_START_THRESHOLD
200
+ )
201
+
192
202
  def update_state(self):
193
203
  """Updates the current state of the state machine."""
194
204
  self.current_state = States(self.state) # type: ignore
@@ -227,6 +237,32 @@ class StateMachine(object):
227
237
  f"Task: {str(task.id)[:8]} was reported as task.status by the robot"
228
238
  )
229
239
 
240
+ def publish_mission_aborted(self, reason: str, can_be_continued: bool) -> None:
241
+ if not self.mqtt_publisher:
242
+ return
243
+
244
+ if self.current_mission is None:
245
+ self.logger.warning(
246
+ "Could not publish mission aborted message. No ongoing mission."
247
+ )
248
+ return
249
+
250
+ payload: MissionAbortedPayload = MissionAbortedPayload(
251
+ isar_id=settings.ISAR_ID,
252
+ robot_name=settings.ROBOT_NAME,
253
+ mission_id=self.current_mission.id,
254
+ reason=reason,
255
+ can_be_continued=can_be_continued,
256
+ timestamp=datetime.now(timezone.utc),
257
+ )
258
+
259
+ self.mqtt_publisher.publish(
260
+ topic=settings.TOPIC_ISAR_MISSION_ABORTED,
261
+ payload=json.dumps(payload, cls=EnhancedJSONEncoder),
262
+ qos=1,
263
+ retain=True,
264
+ )
265
+
230
266
  def publish_mission_status(self) -> None:
231
267
  if not self.mqtt_publisher:
232
268
  return
@@ -339,6 +375,8 @@ class StateMachine(object):
339
375
  return RobotStatus.BlockedProtectiveStop
340
376
  elif self.current_state == States.InterventionNeeded:
341
377
  return RobotStatus.InterventionNeeded
378
+ elif self.current_state == States.Recharging:
379
+ return RobotStatus.Recharging
342
380
  else:
343
381
  return RobotStatus.Busy
344
382
 
@@ -25,7 +25,9 @@ class AwaitNextMission(EventHandlerBase):
25
25
  EventHandlerMapping(
26
26
  name="start_mission_event",
27
27
  event=events.api_requests.start_mission.request,
28
- handler=lambda event: start_mission_event_handler(state_machine, event),
28
+ handler=lambda event: start_mission_event_handler(
29
+ state_machine, event, events.api_requests.start_mission.response
30
+ ),
29
31
  ),
30
32
  EventHandlerMapping(
31
33
  name="return_home_event",
@@ -23,7 +23,9 @@ class Home(EventHandlerBase):
23
23
  EventHandlerMapping(
24
24
  name="start_mission_event",
25
25
  event=events.api_requests.start_mission.request,
26
- handler=lambda event: start_mission_event_handler(state_machine, event),
26
+ handler=lambda event: start_mission_event_handler(
27
+ state_machine, event, events.api_requests.start_mission.response
28
+ ),
27
29
  ),
28
30
  EventHandlerMapping(
29
31
  name="return_home_event",
@@ -2,6 +2,7 @@ import logging
2
2
  from copy import deepcopy
3
3
  from typing import TYPE_CHECKING, Callable, List, Optional
4
4
 
5
+ from isar.config.settings import settings
5
6
  from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
6
7
  from isar.models.events import Event
7
8
  from isar.services.utilities.threaded_request import ThreadedRequest
@@ -23,6 +24,7 @@ class Monitor(EventHandlerBase):
23
24
  def __init__(self, state_machine: "StateMachine"):
24
25
  logger = logging.getLogger("state_machine")
25
26
  events = state_machine.events
27
+ shared_state = state_machine.shared_state
26
28
 
27
29
  def _pause_mission_event_handler(event: Event[bool]) -> Optional[Callable]:
28
30
  if event.consume_event():
@@ -46,6 +48,23 @@ class Monitor(EventHandlerBase):
46
48
  return state_machine.mission_finished # type: ignore
47
49
  return None
48
50
 
51
+ def _robot_battery_level_updated_handler(
52
+ event: Event[float],
53
+ ) -> Optional[Callable]:
54
+ battery_level: float = event.check()
55
+ if battery_level < settings.ROBOT_MISSION_BATTERY_START_THRESHOLD:
56
+ state_machine.publish_mission_aborted(
57
+ "Robot battery too low to continue mission", True
58
+ )
59
+ state_machine._finalize()
60
+ state_machine.logger.warning(
61
+ "Cancelling current mission due to low battery"
62
+ )
63
+ state_machine.current_mission = None
64
+ state_machine.current_task = None
65
+ return state_machine.request_return_home # type: ignore
66
+ return None
67
+
49
68
  event_handlers: List[EventHandlerMapping] = [
50
69
  EventHandlerMapping(
51
70
  name="stop_mission_event",
@@ -85,6 +104,11 @@ class Monitor(EventHandlerBase):
85
104
  state_machine, _handle_task_completed, event
86
105
  ),
87
106
  ),
107
+ EventHandlerMapping(
108
+ name="robot_battery_update_event",
109
+ event=shared_state.robot_battery_level,
110
+ handler=_robot_battery_level_updated_handler,
111
+ ),
88
112
  ]
89
113
  super().__init__(
90
114
  state_name="monitor",