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.
- {isar-1.32.3 → isar-1.33.1}/PKG-INFO +1 -1
- isar-1.33.1/docs/full_state_machine_diagram.png +0 -0
- isar-1.33.1/docs/mission_state_machine_diagram.png +0 -0
- isar-1.33.1/docs/robot_status_state_machine_diagram.png +0 -0
- {isar-1.32.3 → isar-1.33.1}/docs/update_state_diagram.py +53 -3
- {isar-1.32.3 → isar-1.33.1}/src/isar/apis/models/models.py +6 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/apis/schedule/scheduling_controller.py +21 -52
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/open_telemetry.py +52 -12
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/settings.py +17 -3
- {isar-1.32.3 → isar-1.33.1}/src/isar/eventhandlers/eventhandler.py +22 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/models/events.py +56 -27
- {isar-1.32.3 → isar-1.33.1}/src/isar/robot/robot_status.py +3 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/utilities/scheduling_utilities.py +52 -21
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/state_machine.py +39 -1
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/await_next_mission.py +3 -1
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/home.py +3 -1
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/monitor.py +22 -0
- isar-1.33.1/src/isar/state_machine/states/recharging.py +44 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/returning_home.py +15 -1
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/robot_standing_still.py +3 -1
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/stopping.py +33 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states_enum.py +1 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/pause.py +1 -1
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/resume.py +1 -1
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/start_mission.py +11 -3
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/stop.py +3 -30
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/mission.py +0 -2
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/return_home.py +11 -1
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/robot_status.py +10 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/utils/common_event_handlers.py +16 -4
- {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/PKG-INFO +1 -1
- {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/SOURCES.txt +1 -1
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/inspection/inspection.py +6 -15
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/mission/status.py +1 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/robot_interface.py +27 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/telemetry/payloads.py +10 -0
- {isar-1.32.3 → isar-1.33.1}/tests/conftest.py +0 -2
- {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/scheduler/test_scheduler_router.py +7 -7
- {isar-1.32.3 → isar-1.33.1}/tests/isar/models/communication/test_events.py +3 -3
- {isar-1.32.3 → isar-1.33.1}/tests/isar/services/utilities/test_queue_utilities.py +2 -2
- {isar-1.32.3 → isar-1.33.1}/tests/isar/services/utilities/test_scheduling_utilities.py +65 -1
- {isar-1.32.3 → isar-1.33.1}/tests/isar/state_machine/test_state_machine.py +189 -1
- {isar-1.32.3 → isar-1.33.1}/tests/test_double/robot_interface.py +3 -0
- isar-1.32.3/docs/full_state_machine_diagram.png +0 -0
- isar-1.32.3/docs/mission_state_machine_diagram.png +0 -0
- isar-1.32.3/docs/robot_status_state_machine_diagram.png +0 -0
- isar-1.32.3/tests/test_double/mqtt_client.py +0 -10
- {isar-1.32.3 → isar-1.33.1}/.dockerignore +0 -0
- {isar-1.32.3 → isar-1.33.1}/.env.test +0 -0
- {isar-1.32.3 → isar-1.33.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {isar-1.32.3 → isar-1.33.1}/.github/ISSUE_TEMPLATE/feature.md +0 -0
- {isar-1.32.3 → isar-1.33.1}/.github/ISSUE_TEMPLATE/improvement.md +0 -0
- {isar-1.32.3 → isar-1.33.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {isar-1.32.3 → isar-1.33.1}/.github/release.yml +0 -0
- {isar-1.32.3 → isar-1.33.1}/.github/workflows/compile_requirements.yml +0 -0
- {isar-1.32.3 → isar-1.33.1}/.github/workflows/project_automations.yml +0 -0
- {isar-1.32.3 → isar-1.33.1}/.github/workflows/pythonpackage.yml +0 -0
- {isar-1.32.3 → isar-1.33.1}/.github/workflows/pythonpublish.yml +0 -0
- {isar-1.32.3 → isar-1.33.1}/.github/workflows/stale.yml +0 -0
- {isar-1.32.3 → isar-1.33.1}/.gitignore +0 -0
- {isar-1.32.3 → isar-1.33.1}/.pre-commit-config.yaml +0 -0
- {isar-1.32.3 → isar-1.33.1}/LICENSE +0 -0
- {isar-1.32.3 → isar-1.33.1}/README.md +0 -0
- {isar-1.32.3 → isar-1.33.1}/SECURITY.md +0 -0
- {isar-1.32.3 → isar-1.33.1}/docs/Makefile +0 -0
- {isar-1.32.3 → isar-1.33.1}/docs/make.bat +0 -0
- {isar-1.32.3 → isar-1.33.1}/docs/rst_processing.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/docs/source/conf.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/docs/source/index.rst +0 -0
- {isar-1.32.3 → isar-1.33.1}/docs/source/readme_link.md +0 -0
- {isar-1.32.3 → isar-1.33.1}/main.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/pyproject.toml +0 -0
- {isar-1.32.3 → isar-1.33.1}/radixconfig.yml +0 -0
- {isar-1.32.3 → isar-1.33.1}/requirements.txt +0 -0
- {isar-1.32.3 → isar-1.33.1}/setup.cfg +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/apis/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/apis/api.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/apis/models/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/apis/models/start_mission_definition.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/apis/robot_control/robot_controller.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/apis/schedule/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/apis/security/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/apis/security/authentication.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/certs/ca-cert.pem +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/configuration_error.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/keyvault/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/keyvault/keyvault_error.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/keyvault/keyvault_service.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/log.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/logging.conf +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/JSP1_intermediate_deck.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/JSP1_weather_deck.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/default_map.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/klab_b.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/klab_compressor.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/klab_turtlebot.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/maps/turtleworld.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_mission_definition/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_mission_definition/default_exr.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_mission_definition/default_mission.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_mission_definition/default_turtlebot.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_missions/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_missions/default.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/config/predefined_missions/default_turtlebot.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/mission_planner/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/mission_planner/local_planner.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/mission_planner/mission_planner_interface.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/mission_planner/sequential_task_selector.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/mission_planner/task_selector_interface.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/models/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/modules.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/robot/robot.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/robot/robot_start_mission.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/robot/robot_stop_mission.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/robot/robot_task_status.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/script.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/auth/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/auth/azure_credentials.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/mqtt/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/mqtt/mqtt_client.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/mqtt/robot_info_publisher.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/service_connections/request_handler.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/utilities/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/utilities/robot_utilities.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/services/utilities/threaded_request.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/blocked_protective_stop.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/intervention_needed.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/offline.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/paused.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/states/unknown_status.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/fail_mission.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/finish_mission.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/return_home.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/robot_status.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/state_machine/transitions/functions/utils.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/storage/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/storage/blob_storage.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/storage/local_storage.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/storage/storage_interface.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/storage/uploader.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar/storage/utilities.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/dependency_links.txt +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/entry_points.txt +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/requires.txt +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/isar.egg-info/top_level.txt +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/exceptions/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/exceptions/robot_exceptions.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/initialize/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/inspection/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/mission/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/mission/mission.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/mission/task.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/robots/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/robots/battery_state.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/robots/media.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/models/robots/robot_model.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/telemetry/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/telemetry/mqtt_client.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/test_robot_interface.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/utilities/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/utilities/json_service.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/src/robot_interface/utilities/uuid_string_factory.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/integration/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/config/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/config/maps/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/config/maps/turtleworld.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/config/missions/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/config/missions/default.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/integration/turtlebot/test_successful_mission.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/models/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/models/example_mission_definition.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/models/test_start_mission_definition.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/scheduler/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/security/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/apis/security/test_authentication.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/mission/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/mission/test_mission.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/models/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/models/communication/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/services/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/services/readers/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/services/readers/test_mission_reader.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/services/service_connections/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/services/service_connections/echo/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/services/service_connections/test_base_request_handler.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/services/utilities/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/state_machine/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/state_machine/states/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/state_machine/states/test_monitor.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/storage/test_blob_storage.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/isar/storage/test_uploader.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_data/test_map_config/test_map_config.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_data/test_mission_not_working.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_data/test_mission_working.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_data/test_mission_working_no_tasks.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_data/test_thermal_image_mission.json +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_double/__init__.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_double/blob_storage.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_double/mission_definition.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_double/pose.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_double/request.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_double/status.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_double/task.py +0 -0
- {isar-1.32.3 → isar-1.33.1}/tests/test_double/token.py +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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(
|
|
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.
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
state
|
|
82
|
+
mission: Mission = to_isar_mission(
|
|
83
|
+
start_mission_definition=mission_definition
|
|
94
84
|
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
93
|
+
self.scheduling_utilities.verify_robot_capable_of_mission(
|
|
94
|
+
mission=mission, robot_capabilities=robot_settings.CAPABILITIES
|
|
95
|
+
)
|
|
115
96
|
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
#
|
|
16
|
-
|
|
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=
|
|
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
|
-
|
|
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,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
self.
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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 =
|
|
185
|
-
|
|
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
|
-
|
|
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 =
|
|
230
|
-
|
|
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 =
|
|
286
|
-
|
|
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.
|
|
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()
|