isar 1.17.0__tar.gz → 1.19.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.
- {isar-1.17.0 → isar-1.19.0}/.github/workflows/stale.yml +1 -1
- {isar-1.17.0 → isar-1.19.0}/PKG-INFO +12 -1
- {isar-1.17.0 → isar-1.19.0}/README.md +11 -0
- {isar-1.17.0 → isar-1.19.0}/main.py +0 -12
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/settings.py +5 -4
- {isar-1.17.0 → isar-1.19.0}/src/isar/state_machine/state_machine.py +39 -5
- {isar-1.17.0 → isar-1.19.0}/src/isar/state_machine/states/__init__.py +1 -0
- isar-1.19.0/src/isar/state_machine/states/idle.py +85 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/state_machine/states/monitor.py +36 -3
- isar-1.19.0/src/isar/state_machine/states/offline.py +62 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/state_machine/states_enum.py +1 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar.egg-info/PKG-INFO +12 -1
- {isar-1.17.0 → isar-1.19.0}/src/isar.egg-info/SOURCES.txt +1 -2
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/exceptions/robot_exceptions.py +13 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/state_machine/test_state_machine.py +28 -1
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/robot_interface.py +12 -0
- isar-1.17.0/.github/workflows/repository_dispatch_on_merge.yml +0 -20
- isar-1.17.0/src/isar/services/service_connections/mqtt/robot_status_publisher.py +0 -119
- isar-1.17.0/src/isar/state_machine/states/idle.py +0 -40
- {isar-1.17.0 → isar-1.19.0}/.dockerignore +0 -0
- {isar-1.17.0 → isar-1.19.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {isar-1.17.0 → isar-1.19.0}/.github/ISSUE_TEMPLATE/feature.md +0 -0
- {isar-1.17.0 → isar-1.19.0}/.github/ISSUE_TEMPLATE/improvement.md +0 -0
- {isar-1.17.0 → isar-1.19.0}/.github/release.yml +0 -0
- {isar-1.17.0 → isar-1.19.0}/.github/workflows/project_automations.yml +0 -0
- {isar-1.17.0 → isar-1.19.0}/.github/workflows/publish_isar_base_image.yml +0 -0
- {isar-1.17.0 → isar-1.19.0}/.github/workflows/pythonpackage.yml +0 -0
- {isar-1.17.0 → isar-1.19.0}/.github/workflows/pythonpublish.yml +0 -0
- {isar-1.17.0 → isar-1.19.0}/.gitignore +0 -0
- {isar-1.17.0 → isar-1.19.0}/.pre-commit-config.yaml +0 -0
- {isar-1.17.0 → isar-1.19.0}/Dockerfile +0 -0
- {isar-1.17.0 → isar-1.19.0}/LICENSE +0 -0
- {isar-1.17.0 → isar-1.19.0}/SECURITY.md +0 -0
- {isar-1.17.0 → isar-1.19.0}/docker-compose-turtlebot.yml +0 -0
- {isar-1.17.0 → isar-1.19.0}/docker-compose.yml +0 -0
- {isar-1.17.0 → isar-1.19.0}/docs/Makefile +0 -0
- {isar-1.17.0 → isar-1.19.0}/docs/make.bat +0 -0
- {isar-1.17.0 → isar-1.19.0}/docs/rst_processing.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/docs/source/conf.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/docs/source/index.rst +0 -0
- {isar-1.17.0 → isar-1.19.0}/docs/source/readme_link.md +0 -0
- {isar-1.17.0 → isar-1.19.0}/docs/state_machine_diagram.png +0 -0
- {isar-1.17.0 → isar-1.19.0}/pyproject.toml +0 -0
- {isar-1.17.0 → isar-1.19.0}/radixconfig.yml +0 -0
- {isar-1.17.0 → isar-1.19.0}/setup.cfg +0 -0
- {isar-1.17.0 → isar-1.19.0}/setup.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/apis/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/apis/api.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/apis/models/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/apis/models/models.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/apis/models/start_mission_definition.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/apis/schedule/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/apis/schedule/scheduling_controller.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/apis/security/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/apis/security/authentication.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/certs/ca-cert.pem +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/configuration_error.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/keyvault/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/keyvault/keyvault_error.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/keyvault/keyvault_service.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/log.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/logging.conf +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/maps/JSP1_intermediate_deck.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/maps/JSP1_weather_deck.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/maps/default_map.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/maps/klab_b.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/maps/klab_compressor.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/maps/klab_turtlebot.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/maps/turtleworld.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/predefined_mission_definition/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/predefined_mission_definition/default_exr.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/predefined_mission_definition/default_mission.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/predefined_mission_definition/default_turtlebot.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/predefined_missions/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/predefined_missions/default.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/predefined_missions/default_turtlebot.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/predefined_poses/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/predefined_poses/predefined_poses.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/config/settings.env +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/mission_planner/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/mission_planner/local_planner.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/mission_planner/mission_planner_interface.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/mission_planner/sequential_task_selector.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/mission_planner/task_selector_interface.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/models/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/models/communication/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/models/communication/message.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/models/communication/queues/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/models/communication/queues/queue_io.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/models/communication/queues/queue_timeout_error.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/models/communication/queues/queues.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/models/communication/queues/status_queue.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/models/mission_metadata/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/modules.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/auth/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/auth/azure_credentials.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/readers/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/readers/base_reader.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/service_connections/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/service_connections/mqtt/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/service_connections/mqtt/mqtt_client.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/service_connections/mqtt/robot_info_publisher.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/service_connections/request_handler.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/service_connections/stid/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/utilities/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/utilities/queue_utilities.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/utilities/scheduling_utilities.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/services/utilities/threaded_request.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/state_machine/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/state_machine/states/initialize.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/state_machine/states/initiate.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/state_machine/states/off.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/state_machine/states/paused.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/state_machine/states/stop.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/storage/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/storage/blob_storage.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/storage/local_storage.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/storage/slimm_storage.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/storage/storage_interface.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/storage/uploader.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar/storage/utilities.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar.egg-info/dependency_links.txt +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar.egg-info/requires.txt +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/isar.egg-info/top_level.txt +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/exceptions/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/initialize/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/initialize/initialize_params.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/inspection/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/inspection/inspection.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/mission/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/mission/mission.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/mission/status.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/mission/step.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/mission/task.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/robots/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/models/robots/robot_model.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/robot_interface.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/telemetry/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/telemetry/mqtt_client.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/telemetry/payloads.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/test_robot_interface.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/utilities/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/utilities/json_service.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/src/robot_interface/utilities/uuid_string_factory.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/conftest.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/integration/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/integration/turtlebot/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/integration/turtlebot/config/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/integration/turtlebot/config/maps/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/integration/turtlebot/config/maps/turtleworld.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/integration/turtlebot/config/missions/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/integration/turtlebot/config/missions/default.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/integration/turtlebot/test_successful_mission.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/apis/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/apis/scheduler/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/apis/scheduler/test_scheduler_router.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/apis/security/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/apis/security/test_authentication.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/mission/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/mission/test_mission.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/models/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/models/communication/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/models/communication/test_queues.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/models/example_mission_definition.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/models/test_start_mission_definition.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/services/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/services/readers/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/services/readers/test_base_reader.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/services/readers/test_mission_reader.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/services/service_connections/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/services/service_connections/echo/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/services/service_connections/test_base_request_handler.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/services/utilities/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/services/utilities/test_queue_utilities.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/services/utilities/test_scheduling_utilities.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/state_machine/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/state_machine/states/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/state_machine/states/test_monitor.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/storage/test_blob_storage.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/isar/storage/test_uploader.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/__init__.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/blob_storage.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/mission_definition.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/mqtt_client.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/pose.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/request.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/status.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/step.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/task.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/mocks/token.py +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/test_data/test_json_file.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/test_data/test_map_config/test_map_config.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/test_data/test_mission_not_working.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/test_data/test_mission_working.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/test_data/test_mission_working_no_tasks.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/test_data/test_thermal_image_mission.json +0 -0
- {isar-1.17.0 → isar-1.19.0}/tests/test_modules.py +0 -0
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
|
|
21
21
|
steps:
|
|
22
22
|
- name: Close Stale Issues
|
|
23
|
-
uses: actions/stale@
|
|
23
|
+
uses: actions/stale@v9.0.0
|
|
24
24
|
with:
|
|
25
25
|
repo-token: ${{ secrets.STALE_ISSUE_TOKEN }}
|
|
26
26
|
stale-issue-message: 'This issue has automatically been marked as stale as there has been no activity for 60 days.'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: isar
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.19.0
|
|
4
4
|
Summary: Integration and Supervisory control of Autonomous Robots
|
|
5
5
|
Home-page: https://github.com/equinor/isar
|
|
6
6
|
Author: Equinor ASA
|
|
@@ -415,6 +415,17 @@ ISAR_MQTT_PASSWORD
|
|
|
415
415
|
|
|
416
416
|
If not specified the password will default to an empty string.
|
|
417
417
|
|
|
418
|
+
## Running several ISAR instances locally
|
|
419
|
+
|
|
420
|
+
To run several ISAR instances in parallel locally:
|
|
421
|
+
1. Generate a guid: https://www.guidgenerator.com/
|
|
422
|
+
2. Open a new terminal in the isar folder
|
|
423
|
+
3. Run the following command before running main.py:
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
export ISAR_API_PORT=port_name_higher_than_1024 ISAR_ISAR_ID=guid ISAR_ROBOT_NAME=random_robot_name
|
|
427
|
+
```
|
|
428
|
+
|
|
418
429
|
# Contributions
|
|
419
430
|
|
|
420
431
|
Equinor welcomes all kinds of contributions, including code, bug reports, issues, feature requests, and documentation
|
|
@@ -361,6 +361,17 @@ ISAR_MQTT_PASSWORD
|
|
|
361
361
|
|
|
362
362
|
If not specified the password will default to an empty string.
|
|
363
363
|
|
|
364
|
+
## Running several ISAR instances locally
|
|
365
|
+
|
|
366
|
+
To run several ISAR instances in parallel locally:
|
|
367
|
+
1. Generate a guid: https://www.guidgenerator.com/
|
|
368
|
+
2. Open a new terminal in the isar folder
|
|
369
|
+
3. Run the following command before running main.py:
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
export ISAR_API_PORT=port_name_higher_than_1024 ISAR_ISAR_ID=guid ISAR_ROBOT_NAME=random_robot_name
|
|
373
|
+
```
|
|
374
|
+
|
|
364
375
|
# Contributions
|
|
365
376
|
|
|
366
377
|
Equinor welcomes all kinds of contributions, including code, bug reports, issues, feature requests, and documentation
|
|
@@ -19,9 +19,6 @@ from isar.services.service_connections.mqtt.robot_heartbeat_publisher import (
|
|
|
19
19
|
from isar.services.service_connections.mqtt.robot_info_publisher import (
|
|
20
20
|
RobotInfoPublisher,
|
|
21
21
|
)
|
|
22
|
-
from isar.services.service_connections.mqtt.robot_status_publisher import (
|
|
23
|
-
RobotStatusPublisher,
|
|
24
|
-
)
|
|
25
22
|
from isar.state_machine.state_machine import StateMachine, main
|
|
26
23
|
from isar.storage.uploader import Uploader
|
|
27
24
|
from robot_interface.robot_interface import RobotInterface
|
|
@@ -56,15 +53,6 @@ if __name__ == "__main__":
|
|
|
56
53
|
target=mqtt_client.run, name="ISAR MQTT Client", daemon=True
|
|
57
54
|
)
|
|
58
55
|
threads.append(mqtt_thread)
|
|
59
|
-
robot_status_publisher: RobotStatusPublisher = RobotStatusPublisher(
|
|
60
|
-
mqtt_queue=queues.mqtt_queue, robot=robot, state_machine=state_machine
|
|
61
|
-
)
|
|
62
|
-
robot_status_thread: Thread = Thread(
|
|
63
|
-
target=robot_status_publisher.run,
|
|
64
|
-
name="ISAR Robot Status Publisher",
|
|
65
|
-
daemon=True,
|
|
66
|
-
)
|
|
67
|
-
threads.append(robot_status_thread)
|
|
68
56
|
|
|
69
57
|
robot_info_publisher: RobotInfoPublisher = RobotInfoPublisher(
|
|
70
58
|
mqtt_queue=queues.mqtt_queue
|
|
@@ -56,6 +56,9 @@ class Settings(BaseSettings):
|
|
|
56
56
|
# Number of attempts to initiate a step or mission before cancelling
|
|
57
57
|
INITIATE_FAILURE_COUNTER_LIMIT: int = Field(default=10)
|
|
58
58
|
|
|
59
|
+
# Number of attempts to request a step status in monitor before cancelling
|
|
60
|
+
REQUEST_STATUS_FAILURE_COUNTER_LIMIT: int = Field(default=3)
|
|
61
|
+
|
|
59
62
|
# Number of attempts to stop the robot before giving up
|
|
60
63
|
STOP_ROBOT_ATTEMPTS_LIMIT: int = Field(default=10)
|
|
61
64
|
|
|
@@ -226,14 +229,13 @@ class Settings(BaseSettings):
|
|
|
226
229
|
DATA_CLASSIFICATION: str = Field(default="internal")
|
|
227
230
|
|
|
228
231
|
# List of MQTT Topics
|
|
229
|
-
|
|
232
|
+
TOPIC_ISAR_STATUS: str = Field(default="status", validate_default=True)
|
|
230
233
|
TOPIC_ISAR_MISSION: str = Field(default="mission", validate_default=True)
|
|
231
234
|
TOPIC_ISAR_TASK: str = Field(default="task", validate_default=True)
|
|
232
235
|
TOPIC_ISAR_STEP: str = Field(default="step", validate_default=True)
|
|
233
236
|
TOPIC_ISAR_INSPECTION_RESULT: str = Field(
|
|
234
237
|
default="inspection_result", validate_default=True
|
|
235
238
|
)
|
|
236
|
-
TOPIC_ISAR_ROBOT_STATUS: str = Field(default="robot_status", validate_default=True)
|
|
237
239
|
TOPIC_ISAR_ROBOT_INFO: str = Field(default="robot_info", validate_default=True)
|
|
238
240
|
TOPIC_ISAR_ROBOT_HEARTBEAT: str = Field(
|
|
239
241
|
default="robot_heartbeat", validate_default=True
|
|
@@ -281,11 +283,10 @@ class Settings(BaseSettings):
|
|
|
281
283
|
}
|
|
282
284
|
|
|
283
285
|
@field_validator(
|
|
284
|
-
"
|
|
286
|
+
"TOPIC_ISAR_STATUS",
|
|
285
287
|
"TOPIC_ISAR_MISSION",
|
|
286
288
|
"TOPIC_ISAR_TASK",
|
|
287
289
|
"TOPIC_ISAR_STEP",
|
|
288
|
-
"TOPIC_ISAR_ROBOT_STATUS",
|
|
289
290
|
"TOPIC_ISAR_ROBOT_INFO",
|
|
290
291
|
"TOPIC_ISAR_ROBOT_HEARTBEAT",
|
|
291
292
|
"TOPIC_ISAR_INSPECTION_RESULT",
|
|
@@ -24,6 +24,7 @@ from isar.state_machine.states import (
|
|
|
24
24
|
Initiate,
|
|
25
25
|
Monitor,
|
|
26
26
|
Off,
|
|
27
|
+
Offline,
|
|
27
28
|
Paused,
|
|
28
29
|
Stop,
|
|
29
30
|
)
|
|
@@ -31,7 +32,12 @@ from isar.state_machine.states_enum import States
|
|
|
31
32
|
from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
|
|
32
33
|
from robot_interface.models.initialize.initialize_params import InitializeParams
|
|
33
34
|
from robot_interface.models.mission.mission import Mission
|
|
34
|
-
from robot_interface.models.mission.status import
|
|
35
|
+
from robot_interface.models.mission.status import (
|
|
36
|
+
MissionStatus,
|
|
37
|
+
RobotStatus,
|
|
38
|
+
StepStatus,
|
|
39
|
+
TaskStatus,
|
|
40
|
+
)
|
|
35
41
|
from robot_interface.models.mission.step import Step
|
|
36
42
|
from robot_interface.models.mission.task import Task
|
|
37
43
|
from robot_interface.robot_interface import RobotInterface
|
|
@@ -88,6 +94,7 @@ class StateMachine(object):
|
|
|
88
94
|
self.monitor_state: State = Monitor(self)
|
|
89
95
|
self.initiate_state: State = Initiate(self)
|
|
90
96
|
self.off_state: State = Off(self)
|
|
97
|
+
self.offline_state: State = Offline(self)
|
|
91
98
|
|
|
92
99
|
self.states: List[State] = [
|
|
93
100
|
self.off_state,
|
|
@@ -97,6 +104,7 @@ class StateMachine(object):
|
|
|
97
104
|
self.monitor_state,
|
|
98
105
|
self.stop_state,
|
|
99
106
|
self.paused_state,
|
|
107
|
+
self.offline_state,
|
|
100
108
|
]
|
|
101
109
|
|
|
102
110
|
self.machine = Machine(self, states=self.states, initial="off", queued=True)
|
|
@@ -194,6 +202,18 @@ class StateMachine(object):
|
|
|
194
202
|
"dest": self.idle_state,
|
|
195
203
|
"before": self._mission_stopped,
|
|
196
204
|
},
|
|
205
|
+
{
|
|
206
|
+
"trigger": "robot_turned_offline",
|
|
207
|
+
"source": [self.idle_state],
|
|
208
|
+
"dest": self.offline_state,
|
|
209
|
+
"before": self._offline,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"trigger": "robot_turned_online",
|
|
213
|
+
"source": self.offline_state,
|
|
214
|
+
"dest": self.idle_state,
|
|
215
|
+
"before": self._online,
|
|
216
|
+
},
|
|
197
217
|
]
|
|
198
218
|
)
|
|
199
219
|
|
|
@@ -239,6 +259,12 @@ class StateMachine(object):
|
|
|
239
259
|
def _off(self) -> None:
|
|
240
260
|
return
|
|
241
261
|
|
|
262
|
+
def _offline(self) -> None:
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
def _online(self) -> None:
|
|
266
|
+
return
|
|
267
|
+
|
|
242
268
|
def _resume(self) -> None:
|
|
243
269
|
self.logger.info(f"Resuming mission: {self.current_mission.id}")
|
|
244
270
|
self.current_mission.status = MissionStatus.InProgress
|
|
@@ -417,7 +443,7 @@ class StateMachine(object):
|
|
|
417
443
|
self.send_state_status()
|
|
418
444
|
self._log_state_transition(self.current_state)
|
|
419
445
|
self.logger.info(f"State: {self.current_state}")
|
|
420
|
-
self.
|
|
446
|
+
self.publish_status()
|
|
421
447
|
|
|
422
448
|
def reset_state_machine(self) -> None:
|
|
423
449
|
self.logger.info("Resetting state machine")
|
|
@@ -559,25 +585,33 @@ class StateMachine(object):
|
|
|
559
585
|
retain=False,
|
|
560
586
|
)
|
|
561
587
|
|
|
562
|
-
def
|
|
588
|
+
def publish_status(self) -> None:
|
|
563
589
|
if not self.mqtt_publisher:
|
|
564
590
|
return
|
|
565
591
|
payload: str = json.dumps(
|
|
566
592
|
{
|
|
567
593
|
"isar_id": settings.ISAR_ID,
|
|
568
594
|
"robot_name": settings.ROBOT_NAME,
|
|
569
|
-
"
|
|
595
|
+
"status": self._current_status(),
|
|
570
596
|
"timestamp": datetime.utcnow(),
|
|
571
597
|
},
|
|
572
598
|
cls=EnhancedJSONEncoder,
|
|
573
599
|
)
|
|
574
600
|
|
|
575
601
|
self.mqtt_publisher.publish(
|
|
576
|
-
topic=settings.
|
|
602
|
+
topic=settings.TOPIC_ISAR_STATUS,
|
|
577
603
|
payload=payload,
|
|
578
604
|
retain=False,
|
|
579
605
|
)
|
|
580
606
|
|
|
607
|
+
def _current_status(self) -> RobotStatus:
|
|
608
|
+
if self.current_state == States.Idle:
|
|
609
|
+
return RobotStatus.Available
|
|
610
|
+
elif self.current_state == States.Offline:
|
|
611
|
+
return RobotStatus.Offline
|
|
612
|
+
else:
|
|
613
|
+
return RobotStatus.Busy
|
|
614
|
+
|
|
581
615
|
def _log_state_transition(self, next_state):
|
|
582
616
|
"""Logs all state transitions that are not self-transitions."""
|
|
583
617
|
self.transitions_list.append(next_state)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from transitions import State
|
|
6
|
+
|
|
7
|
+
from isar.config.settings import settings
|
|
8
|
+
from isar.models.communication.message import StartMissionMessage
|
|
9
|
+
from isar.services.utilities.threaded_request import (
|
|
10
|
+
ThreadedRequest,
|
|
11
|
+
ThreadedRequestNotFinishedError,
|
|
12
|
+
)
|
|
13
|
+
from robot_interface.models.exceptions.robot_exceptions import RobotException
|
|
14
|
+
from robot_interface.models.mission.status import RobotStatus
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from isar.state_machine.state_machine import StateMachine
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Idle(State):
|
|
21
|
+
def __init__(self, state_machine: "StateMachine") -> None:
|
|
22
|
+
super().__init__(name="idle", on_enter=self.start, on_exit=self.stop)
|
|
23
|
+
self.state_machine: "StateMachine" = state_machine
|
|
24
|
+
self.logger = logging.getLogger("state_machine")
|
|
25
|
+
self.robot_status_thread: Optional[ThreadedRequest] = None
|
|
26
|
+
self.last_robot_status_poll_time: float = time.time()
|
|
27
|
+
|
|
28
|
+
def start(self) -> None:
|
|
29
|
+
self.state_machine.update_state()
|
|
30
|
+
self._run()
|
|
31
|
+
|
|
32
|
+
def stop(self) -> None:
|
|
33
|
+
if self.robot_status_thread:
|
|
34
|
+
self.robot_status_thread.wait_for_thread()
|
|
35
|
+
self.robot_status_thread = None
|
|
36
|
+
|
|
37
|
+
def _run(self) -> None:
|
|
38
|
+
while True:
|
|
39
|
+
start_mission: Optional[StartMissionMessage] = (
|
|
40
|
+
self.state_machine.should_start_mission()
|
|
41
|
+
)
|
|
42
|
+
if start_mission:
|
|
43
|
+
self.state_machine.start_mission(
|
|
44
|
+
mission=start_mission.mission,
|
|
45
|
+
initial_pose=start_mission.initial_pose,
|
|
46
|
+
)
|
|
47
|
+
transition = self.state_machine.mission_started # type: ignore
|
|
48
|
+
break
|
|
49
|
+
time.sleep(self.state_machine.sleep_time)
|
|
50
|
+
|
|
51
|
+
time_from_last_robot_status_poll = (
|
|
52
|
+
time.time() - self.last_robot_status_poll_time
|
|
53
|
+
)
|
|
54
|
+
if (
|
|
55
|
+
time_from_last_robot_status_poll
|
|
56
|
+
< settings.ROBOT_API_STATUS_POLL_INTERVAL
|
|
57
|
+
):
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
if not self.robot_status_thread:
|
|
61
|
+
self.robot_status_thread = ThreadedRequest(
|
|
62
|
+
request_func=self.state_machine.robot.robot_status
|
|
63
|
+
)
|
|
64
|
+
self.robot_status_thread.start_thread(
|
|
65
|
+
name="State Machine Offline Get Robot Status"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
robot_status: RobotStatus = self.robot_status_thread.get_output()
|
|
70
|
+
except ThreadedRequestNotFinishedError:
|
|
71
|
+
time.sleep(self.state_machine.sleep_time)
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
except (RobotException,) as e:
|
|
75
|
+
self.logger.error(
|
|
76
|
+
f"Failed to get robot status because: {e.error_description}"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
self.last_robot_status_poll_time = time.time()
|
|
80
|
+
|
|
81
|
+
if robot_status == RobotStatus.Offline:
|
|
82
|
+
transition = self.state_machine.robot_turned_offline # type: ignore
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
transition()
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
3
|
from copy import deepcopy
|
|
4
|
-
from typing import Callable, Optional, Sequence,
|
|
4
|
+
from typing import TYPE_CHECKING, Callable, Optional, Sequence, Tuple, Union
|
|
5
5
|
|
|
6
6
|
from injector import inject
|
|
7
7
|
from transitions import State
|
|
8
8
|
|
|
9
9
|
from isar.mission_planner.task_selector_interface import TaskSelectorStop
|
|
10
|
+
from isar.config.settings import settings
|
|
10
11
|
from isar.services.utilities.threaded_request import (
|
|
11
12
|
ThreadedRequest,
|
|
12
13
|
ThreadedRequestNotFinishedError,
|
|
13
14
|
)
|
|
14
15
|
from robot_interface.models.exceptions.robot_exceptions import (
|
|
15
16
|
ErrorMessage,
|
|
17
|
+
RobotCommunicationTimeoutException,
|
|
16
18
|
RobotException,
|
|
17
19
|
RobotMissionStatusException,
|
|
18
20
|
RobotRetrieveInspectionException,
|
|
@@ -32,6 +34,10 @@ class Monitor(State):
|
|
|
32
34
|
def __init__(self, state_machine: "StateMachine") -> None:
|
|
33
35
|
super().__init__(name="monitor", on_enter=self.start, on_exit=self.stop)
|
|
34
36
|
self.state_machine: "StateMachine" = state_machine
|
|
37
|
+
self.request_status_failure_counter: int = 0
|
|
38
|
+
self.request_status_failure_counter_limit: int = (
|
|
39
|
+
settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT
|
|
40
|
+
)
|
|
35
41
|
|
|
36
42
|
self.logger = logging.getLogger("state_machine")
|
|
37
43
|
self.step_status_thread: Optional[ThreadedRequest] = None
|
|
@@ -61,7 +67,6 @@ class Monitor(State):
|
|
|
61
67
|
status_function=self.state_machine.robot.step_status,
|
|
62
68
|
thread_name="State Machine Monitor Get Step Status",
|
|
63
69
|
)
|
|
64
|
-
|
|
65
70
|
try:
|
|
66
71
|
status: Union[StepStatus, MissionStatus] = (
|
|
67
72
|
self.step_status_thread.get_output()
|
|
@@ -70,6 +75,34 @@ class Monitor(State):
|
|
|
70
75
|
time.sleep(self.state_machine.sleep_time)
|
|
71
76
|
continue
|
|
72
77
|
|
|
78
|
+
except RobotCommunicationTimeoutException as e:
|
|
79
|
+
self.state_machine.current_mission.error_message = ErrorMessage(
|
|
80
|
+
error_reason=e.error_reason, error_description=e.error_description
|
|
81
|
+
)
|
|
82
|
+
self.step_status_thread = None
|
|
83
|
+
self.request_status_failure_counter += 1
|
|
84
|
+
self.logger.warning(
|
|
85
|
+
f"Monitoring step {self.state_machine.current_step.id} failed #: "
|
|
86
|
+
f"{self.request_status_failure_counter} failed because: {e.error_description}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
self.request_status_failure_counter
|
|
91
|
+
>= self.request_status_failure_counter_limit
|
|
92
|
+
):
|
|
93
|
+
self.state_machine.current_step.error_message = ErrorMessage(
|
|
94
|
+
error_reason=e.error_reason,
|
|
95
|
+
error_description=e.error_description,
|
|
96
|
+
)
|
|
97
|
+
self.logger.error(
|
|
98
|
+
f"Step will be cancelled after failing to get step status "
|
|
99
|
+
f"{self.request_status_failure_counter} times because: "
|
|
100
|
+
f"{e.error_description}"
|
|
101
|
+
)
|
|
102
|
+
status = StepStatus.Failed
|
|
103
|
+
else:
|
|
104
|
+
continue
|
|
105
|
+
|
|
73
106
|
except RobotStepStatusException as e:
|
|
74
107
|
self.state_machine.current_step.error_message = ErrorMessage(
|
|
75
108
|
error_reason=e.error_reason, error_description=e.error_description
|
|
@@ -123,6 +156,7 @@ class Monitor(State):
|
|
|
123
156
|
else:
|
|
124
157
|
if isinstance(status, StepStatus):
|
|
125
158
|
if self._step_finished(self.state_machine.current_step):
|
|
159
|
+
self.state_machine.update_current_step()
|
|
126
160
|
self.state_machine.current_task.update_task_status()
|
|
127
161
|
else: # If not all steps are done
|
|
128
162
|
self.state_machine.current_task.status = TaskStatus.InProgress
|
|
@@ -131,7 +165,6 @@ class Monitor(State):
|
|
|
131
165
|
self.state_machine.current_task
|
|
132
166
|
)
|
|
133
167
|
if self.state_machine.current_task.status == TaskStatus.Successful:
|
|
134
|
-
self.state_machine.update_remaining_steps()
|
|
135
168
|
try:
|
|
136
169
|
self.state_machine.current_task = (
|
|
137
170
|
self.state_machine.task_selector.next_task()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from transitions import State
|
|
6
|
+
|
|
7
|
+
from isar.config.settings import settings
|
|
8
|
+
from isar.services.utilities.threaded_request import (
|
|
9
|
+
ThreadedRequest,
|
|
10
|
+
ThreadedRequestNotFinishedError,
|
|
11
|
+
)
|
|
12
|
+
from robot_interface.models.exceptions.robot_exceptions import RobotException
|
|
13
|
+
from robot_interface.models.mission.status import RobotStatus
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from isar.state_machine.state_machine import StateMachine
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Offline(State):
|
|
20
|
+
def __init__(self, state_machine: "StateMachine") -> None:
|
|
21
|
+
super().__init__(name="offline", on_enter=self.start, on_exit=self.stop)
|
|
22
|
+
self.state_machine: "StateMachine" = state_machine
|
|
23
|
+
self.logger = logging.getLogger("state_machine")
|
|
24
|
+
self.robot_status_thread: Optional[ThreadedRequest] = None
|
|
25
|
+
|
|
26
|
+
def start(self) -> None:
|
|
27
|
+
self.state_machine.update_state()
|
|
28
|
+
self._run()
|
|
29
|
+
|
|
30
|
+
def stop(self) -> None:
|
|
31
|
+
if self.robot_status_thread:
|
|
32
|
+
self.robot_status_thread.wait_for_thread()
|
|
33
|
+
self.robot_status_thread = None
|
|
34
|
+
|
|
35
|
+
def _run(self) -> None:
|
|
36
|
+
while True:
|
|
37
|
+
if not self.robot_status_thread:
|
|
38
|
+
self.robot_status_thread = ThreadedRequest(
|
|
39
|
+
request_func=self.state_machine.robot.robot_status
|
|
40
|
+
)
|
|
41
|
+
self.robot_status_thread.start_thread(
|
|
42
|
+
name="State Machine Offline Get Robot Status"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
robot_status: RobotStatus = self.robot_status_thread.get_output()
|
|
47
|
+
except ThreadedRequestNotFinishedError:
|
|
48
|
+
time.sleep(self.state_machine.sleep_time)
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
except (RobotException,) as e:
|
|
52
|
+
self.logger.error(
|
|
53
|
+
f"Failed to get robot status because: {e.error_description}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if robot_status != RobotStatus.Offline:
|
|
57
|
+
transition = self.state_machine.robot_turned_online # type: ignore
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
time.sleep(settings.ROBOT_API_STATUS_POLL_INTERVAL)
|
|
61
|
+
|
|
62
|
+
transition()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: isar
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.19.0
|
|
4
4
|
Summary: Integration and Supervisory control of Autonomous Robots
|
|
5
5
|
Home-page: https://github.com/equinor/isar
|
|
6
6
|
Author: Equinor ASA
|
|
@@ -415,6 +415,17 @@ ISAR_MQTT_PASSWORD
|
|
|
415
415
|
|
|
416
416
|
If not specified the password will default to an empty string.
|
|
417
417
|
|
|
418
|
+
## Running several ISAR instances locally
|
|
419
|
+
|
|
420
|
+
To run several ISAR instances in parallel locally:
|
|
421
|
+
1. Generate a guid: https://www.guidgenerator.com/
|
|
422
|
+
2. Open a new terminal in the isar folder
|
|
423
|
+
3. Run the following command before running main.py:
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
export ISAR_API_PORT=port_name_higher_than_1024 ISAR_ISAR_ID=guid ISAR_ROBOT_NAME=random_robot_name
|
|
427
|
+
```
|
|
428
|
+
|
|
418
429
|
# Contributions
|
|
419
430
|
|
|
420
431
|
Equinor welcomes all kinds of contributions, including code, bug reports, issues, feature requests, and documentation
|
|
@@ -20,7 +20,6 @@ setup.py
|
|
|
20
20
|
.github/workflows/publish_isar_base_image.yml
|
|
21
21
|
.github/workflows/pythonpackage.yml
|
|
22
22
|
.github/workflows/pythonpublish.yml
|
|
23
|
-
.github/workflows/repository_dispatch_on_merge.yml
|
|
24
23
|
.github/workflows/stale.yml
|
|
25
24
|
docs/Makefile
|
|
26
25
|
docs/make.bat
|
|
@@ -96,7 +95,6 @@ src/isar/services/service_connections/mqtt/__init__.py
|
|
|
96
95
|
src/isar/services/service_connections/mqtt/mqtt_client.py
|
|
97
96
|
src/isar/services/service_connections/mqtt/robot_heartbeat_publisher.py
|
|
98
97
|
src/isar/services/service_connections/mqtt/robot_info_publisher.py
|
|
99
|
-
src/isar/services/service_connections/mqtt/robot_status_publisher.py
|
|
100
98
|
src/isar/services/service_connections/stid/__init__.py
|
|
101
99
|
src/isar/services/utilities/__init__.py
|
|
102
100
|
src/isar/services/utilities/queue_utilities.py
|
|
@@ -111,6 +109,7 @@ src/isar/state_machine/states/initialize.py
|
|
|
111
109
|
src/isar/state_machine/states/initiate.py
|
|
112
110
|
src/isar/state_machine/states/monitor.py
|
|
113
111
|
src/isar/state_machine/states/off.py
|
|
112
|
+
src/isar/state_machine/states/offline.py
|
|
114
113
|
src/isar/state_machine/states/paused.py
|
|
115
114
|
src/isar/state_machine/states/stop.py
|
|
116
115
|
src/isar/storage/__init__.py
|
|
@@ -5,6 +5,7 @@ from typing import Optional
|
|
|
5
5
|
|
|
6
6
|
class ErrorReason(str, Enum):
|
|
7
7
|
RobotCommunicationException: str = "robot_communication_exception"
|
|
8
|
+
RobotCommunicationTimeoutException: str = "robot_communication_timeout_exception"
|
|
8
9
|
RobotInfeasibleStepException: str = "robot_infeasible_step_exception"
|
|
9
10
|
RobotInfeasibleMissionException: str = "robot_infeasible_mission_exception"
|
|
10
11
|
RobotMissionStatusException: str = "robot_mission_status_exception"
|
|
@@ -49,6 +50,18 @@ class RobotCommunicationException(RobotException):
|
|
|
49
50
|
pass
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
# An exception which should be thrown by the robot package if the communication has timed
|
|
54
|
+
# out and ISAR should retry the request.
|
|
55
|
+
class RobotCommunicationTimeoutException(RobotException):
|
|
56
|
+
def __init__(self, error_description: str) -> None:
|
|
57
|
+
super().__init__(
|
|
58
|
+
error_reason=ErrorReason.RobotCommunicationTimeoutException,
|
|
59
|
+
error_description=error_description,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
52
65
|
# An exception which should be thrown by the robot package if it is unable to start the
|
|
53
66
|
# current step.
|
|
54
67
|
class RobotInfeasibleStepException(RobotException):
|
|
@@ -25,7 +25,7 @@ from robot_interface.models.mission.step import DriveToPose, Step, TakeImage
|
|
|
25
25
|
from robot_interface.models.mission.task import Task
|
|
26
26
|
from robot_interface.telemetry.mqtt_client import MqttClientInterface
|
|
27
27
|
from tests.mocks.pose import MockPose
|
|
28
|
-
from tests.mocks.robot_interface import MockRobot
|
|
28
|
+
from tests.mocks.robot_interface import MockRobot, MockRobotIdleToOfflineToIdleTest
|
|
29
29
|
from tests.mocks.step import MockStep
|
|
30
30
|
|
|
31
31
|
|
|
@@ -35,6 +35,8 @@ class StateMachineThread(object):
|
|
|
35
35
|
self.state_machine: StateMachine = injector.get(StateMachine)
|
|
36
36
|
self._thread: Thread = Thread(target=main, args=[self.state_machine])
|
|
37
37
|
self._thread.daemon = True
|
|
38
|
+
|
|
39
|
+
def start(self):
|
|
38
40
|
self._thread.start()
|
|
39
41
|
|
|
40
42
|
|
|
@@ -105,6 +107,7 @@ def test_state_machine_transitions(
|
|
|
105
107
|
mission: Mission = Mission(tasks=[Task(steps=[step_1, step_2])]) # type: ignore
|
|
106
108
|
|
|
107
109
|
state_machine_thread.state_machine.stepwise_mission = should_run_stepwise
|
|
110
|
+
state_machine_thread.start()
|
|
108
111
|
|
|
109
112
|
scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities)
|
|
110
113
|
scheduling_utilities.start_mission(mission=mission, initial_pose=None)
|
|
@@ -143,6 +146,7 @@ def test_state_machine_transitions_when_running_full_mission(
|
|
|
143
146
|
injector, state_machine_thread
|
|
144
147
|
) -> None:
|
|
145
148
|
state_machine_thread.state_machine.stepwise_mission = False
|
|
149
|
+
state_machine_thread.start()
|
|
146
150
|
|
|
147
151
|
step_1: Step = DriveToPose(pose=MockPose.default_pose())
|
|
148
152
|
step_2: Step = TakeImage(target=MockPose.default_pose().position)
|
|
@@ -176,6 +180,8 @@ def test_state_machine_failed_dependency(
|
|
|
176
180
|
|
|
177
181
|
mocker.patch.object(MockRobot, "step_status", return_value=StepStatus.Failed)
|
|
178
182
|
|
|
183
|
+
state_machine_thread.start()
|
|
184
|
+
|
|
179
185
|
scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities)
|
|
180
186
|
scheduling_utilities.start_mission(mission=mission, initial_pose=None)
|
|
181
187
|
|
|
@@ -198,6 +204,8 @@ def test_state_machine_failed_dependency(
|
|
|
198
204
|
def test_state_machine_with_successful_collection(
|
|
199
205
|
injector, state_machine_thread, uploader_thread
|
|
200
206
|
) -> None:
|
|
207
|
+
state_machine_thread.start()
|
|
208
|
+
|
|
201
209
|
storage_mock: StorageInterface = injector.get(List[StorageInterface])[0]
|
|
202
210
|
|
|
203
211
|
step: TakeImage = MockStep.take_image_in_coordinate_direction()
|
|
@@ -230,6 +238,8 @@ def test_state_machine_with_unsuccessful_collection(
|
|
|
230
238
|
|
|
231
239
|
mocker.patch.object(MockRobot, "get_inspections", return_value=[])
|
|
232
240
|
|
|
241
|
+
state_machine_thread.start()
|
|
242
|
+
|
|
233
243
|
step: TakeImage = MockStep.take_image_in_coordinate_direction()
|
|
234
244
|
mission: Mission = Mission(tasks=[Task(steps=[step])])
|
|
235
245
|
scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities)
|
|
@@ -259,6 +269,8 @@ def test_state_machine_with_successful_mission_stop(
|
|
|
259
269
|
state_machine_thread: StateMachineThread,
|
|
260
270
|
caplog: pytest.LogCaptureFixture,
|
|
261
271
|
) -> None:
|
|
272
|
+
state_machine_thread.start()
|
|
273
|
+
|
|
262
274
|
step: TakeImage = MockStep.take_image_in_coordinate_direction()
|
|
263
275
|
mission: Mission = Mission(tasks=[Task(steps=[step])])
|
|
264
276
|
|
|
@@ -298,6 +310,8 @@ def test_state_machine_with_unsuccessful_mission_stop(
|
|
|
298
310
|
MockRobot, "stop", side_effect=_mock_robot_exception_with_message
|
|
299
311
|
)
|
|
300
312
|
|
|
313
|
+
state_machine_thread.start()
|
|
314
|
+
|
|
301
315
|
scheduling_utilities.start_mission(mission=mission, initial_pose=None)
|
|
302
316
|
|
|
303
317
|
scheduling_utilities.stop_mission()
|
|
@@ -320,6 +334,19 @@ def test_state_machine_with_unsuccessful_mission_stop(
|
|
|
320
334
|
assert expected == actual
|
|
321
335
|
|
|
322
336
|
|
|
337
|
+
def test_state_machine_idle_to_offline_to_idle(state_machine_thread) -> None:
|
|
338
|
+
state_machine_thread.state_machine.robot = MockRobotIdleToOfflineToIdleTest()
|
|
339
|
+
|
|
340
|
+
state_machine_thread.start()
|
|
341
|
+
# Robot status check happens every 5 seconds by default
|
|
342
|
+
time.sleep(13) # Ensure that robot_status have been called again in Idle
|
|
343
|
+
|
|
344
|
+
expected_transitions_list = deque([States.Idle, States.Offline, States.Idle])
|
|
345
|
+
assert (
|
|
346
|
+
state_machine_thread.state_machine.transitions_list == expected_transitions_list
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
323
350
|
def _mock_robot_exception_with_message() -> RobotException:
|
|
324
351
|
raise RobotException(
|
|
325
352
|
error_reason=ErrorReason.RobotUnknownErrorException,
|
|
@@ -78,3 +78,15 @@ def mock_image_metadata() -> ImageMetadata:
|
|
|
78
78
|
),
|
|
79
79
|
file_type="jpg",
|
|
80
80
|
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class MockRobotIdleToOfflineToIdleTest(MockRobot):
|
|
84
|
+
def __init__(self):
|
|
85
|
+
self.first = True
|
|
86
|
+
|
|
87
|
+
def robot_status(self) -> RobotStatus:
|
|
88
|
+
if self.first:
|
|
89
|
+
self.first = False
|
|
90
|
+
return RobotStatus.Offline
|
|
91
|
+
|
|
92
|
+
return RobotStatus.Available
|