isar 1.15.0__py3-none-any.whl → 1.34.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- isar/__init__.py +2 -5
- isar/apis/api.py +159 -66
- isar/apis/models/__init__.py +0 -1
- isar/apis/models/models.py +22 -12
- isar/apis/models/start_mission_definition.py +128 -123
- isar/apis/robot_control/robot_controller.py +41 -0
- isar/apis/schedule/scheduling_controller.py +135 -121
- isar/apis/security/authentication.py +5 -5
- isar/config/certs/ca-cert.pem +32 -32
- isar/config/keyvault/keyvault_service.py +1 -2
- isar/config/log.py +47 -39
- isar/config/logging.conf +16 -31
- isar/config/open_telemetry.py +102 -0
- isar/config/predefined_mission_definition/default_exr.json +49 -0
- isar/config/predefined_mission_definition/default_mission.json +1 -5
- isar/config/predefined_mission_definition/default_turtlebot.json +4 -11
- isar/config/predefined_missions/default.json +67 -87
- isar/config/predefined_missions/default_extra_capabilities.json +107 -0
- isar/config/settings.py +119 -142
- isar/eventhandlers/eventhandler.py +123 -0
- isar/mission_planner/local_planner.py +6 -20
- isar/mission_planner/mission_planner_interface.py +1 -1
- isar/models/events.py +184 -0
- isar/models/status.py +18 -0
- isar/modules.py +118 -205
- isar/robot/robot.py +377 -0
- isar/robot/robot_battery.py +60 -0
- isar/robot/robot_monitor_mission.py +357 -0
- isar/robot/robot_pause_mission.py +74 -0
- isar/robot/robot_resume_mission.py +67 -0
- isar/robot/robot_start_mission.py +66 -0
- isar/robot/robot_status.py +61 -0
- isar/robot/robot_stop_mission.py +68 -0
- isar/robot/robot_upload_inspection.py +75 -0
- isar/script.py +171 -0
- isar/services/service_connections/mqtt/mqtt_client.py +47 -11
- isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +32 -0
- isar/services/service_connections/mqtt/robot_info_publisher.py +4 -3
- isar/services/service_connections/persistent_memory.py +69 -0
- isar/services/utilities/mqtt_utilities.py +93 -0
- isar/services/utilities/robot_utilities.py +20 -0
- isar/services/utilities/scheduling_utilities.py +393 -65
- isar/state_machine/state_machine.py +227 -486
- isar/state_machine/states/__init__.py +0 -7
- isar/state_machine/states/await_next_mission.py +114 -0
- isar/state_machine/states/blocked_protective_stop.py +60 -0
- isar/state_machine/states/going_to_lockdown.py +95 -0
- isar/state_machine/states/going_to_recharging.py +92 -0
- isar/state_machine/states/home.py +115 -0
- isar/state_machine/states/intervention_needed.py +77 -0
- isar/state_machine/states/lockdown.py +38 -0
- isar/state_machine/states/maintenance.py +36 -0
- isar/state_machine/states/monitor.py +137 -166
- isar/state_machine/states/offline.py +60 -0
- isar/state_machine/states/paused.py +92 -23
- isar/state_machine/states/pausing.py +48 -0
- isar/state_machine/states/pausing_return_home.py +48 -0
- isar/state_machine/states/recharging.py +80 -0
- isar/state_machine/states/resuming.py +57 -0
- isar/state_machine/states/resuming_return_home.py +64 -0
- isar/state_machine/states/return_home_paused.py +109 -0
- isar/state_machine/states/returning_home.py +217 -0
- isar/state_machine/states/stopping.py +61 -0
- isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
- isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
- isar/state_machine/states/stopping_go_to_recharge.py +51 -0
- isar/state_machine/states/stopping_return_home.py +77 -0
- isar/state_machine/states/unknown_status.py +72 -0
- isar/state_machine/states_enum.py +22 -5
- isar/state_machine/transitions/mission.py +192 -0
- isar/state_machine/transitions/return_home.py +106 -0
- isar/state_machine/transitions/robot_status.py +80 -0
- isar/state_machine/utils/common_event_handlers.py +73 -0
- isar/storage/blob_storage.py +71 -45
- isar/storage/local_storage.py +28 -14
- isar/storage/storage_interface.py +28 -6
- isar/storage/uploader.py +184 -55
- isar/storage/utilities.py +35 -27
- isar-1.34.9.dist-info/METADATA +496 -0
- isar-1.34.9.dist-info/RECORD +135 -0
- {isar-1.15.0.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
- isar-1.34.9.dist-info/entry_points.txt +3 -0
- robot_interface/models/exceptions/__init__.py +0 -7
- robot_interface/models/exceptions/robot_exceptions.py +274 -4
- robot_interface/models/initialize/__init__.py +0 -1
- robot_interface/models/inspection/__init__.py +0 -13
- robot_interface/models/inspection/inspection.py +43 -34
- robot_interface/models/mission/mission.py +18 -14
- robot_interface/models/mission/status.py +20 -25
- robot_interface/models/mission/task.py +156 -92
- robot_interface/models/robots/battery_state.py +6 -0
- robot_interface/models/robots/media.py +13 -0
- robot_interface/models/robots/robot_model.py +7 -7
- robot_interface/robot_interface.py +135 -66
- robot_interface/telemetry/mqtt_client.py +84 -12
- robot_interface/telemetry/payloads.py +111 -12
- robot_interface/utilities/json_service.py +7 -1
- isar/config/predefined_missions/default_turtlebot.json +0 -110
- isar/config/predefined_poses/__init__.py +0 -0
- isar/config/predefined_poses/predefined_poses.py +0 -616
- isar/config/settings.env +0 -26
- isar/mission_planner/sequential_task_selector.py +0 -23
- isar/mission_planner/task_selector_interface.py +0 -31
- isar/models/communication/__init__.py +0 -0
- isar/models/communication/message.py +0 -12
- isar/models/communication/queues/__init__.py +0 -4
- isar/models/communication/queues/queue_io.py +0 -12
- isar/models/communication/queues/queue_timeout_error.py +0 -2
- isar/models/communication/queues/queues.py +0 -19
- isar/models/communication/queues/status_queue.py +0 -20
- isar/models/mission_metadata/__init__.py +0 -0
- isar/services/readers/__init__.py +0 -0
- isar/services/readers/base_reader.py +0 -37
- isar/services/service_connections/mqtt/robot_status_publisher.py +0 -93
- isar/services/service_connections/stid/__init__.py +0 -0
- isar/services/service_connections/stid/stid_service.py +0 -45
- isar/services/utilities/queue_utilities.py +0 -39
- isar/state_machine/states/idle.py +0 -40
- isar/state_machine/states/initialize.py +0 -60
- isar/state_machine/states/initiate.py +0 -129
- isar/state_machine/states/off.py +0 -18
- isar/state_machine/states/stop.py +0 -78
- isar/storage/slimm_storage.py +0 -181
- isar-1.15.0.dist-info/METADATA +0 -417
- isar-1.15.0.dist-info/RECORD +0 -113
- robot_interface/models/initialize/initialize_params.py +0 -9
- robot_interface/models/mission/step.py +0 -211
- {isar-1.15.0.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
- {isar-1.15.0.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
isar/storage/slimm_storage.py
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import logging
|
|
3
|
-
|
|
4
|
-
from azure.identity import DefaultAzureCredential
|
|
5
|
-
from injector import inject
|
|
6
|
-
from requests import HTTPError, RequestException
|
|
7
|
-
from requests_toolbelt import MultipartEncoder
|
|
8
|
-
|
|
9
|
-
from isar.config.settings import settings
|
|
10
|
-
from robot_interface.models.mission.mission import Mission
|
|
11
|
-
from isar.services.auth.azure_credentials import AzureCredentials
|
|
12
|
-
from isar.services.service_connections.request_handler import RequestHandler
|
|
13
|
-
from isar.storage.storage_interface import StorageException, StorageInterface
|
|
14
|
-
from isar.storage.utilities import get_filename
|
|
15
|
-
from robot_interface.models.inspection.inspection import Inspection, ThermalVideo, Video
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class SlimmStorage(StorageInterface):
|
|
19
|
-
@inject
|
|
20
|
-
def __init__(self, request_handler: RequestHandler) -> None:
|
|
21
|
-
self.request_handler: RequestHandler = request_handler
|
|
22
|
-
self.logger = logging.getLogger("uploader")
|
|
23
|
-
|
|
24
|
-
self.credentials: DefaultAzureCredential = (
|
|
25
|
-
AzureCredentials.get_azure_credentials()
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
client_id: str = settings.SLIMM_CLIENT_ID
|
|
29
|
-
scope: str = settings.SLIMM_APP_SCOPE
|
|
30
|
-
self.request_scope: str = f"{client_id}/{scope}"
|
|
31
|
-
|
|
32
|
-
self.url: str = settings.SLIMM_API_URL
|
|
33
|
-
|
|
34
|
-
def store(self, inspection: Inspection, mission: Mission) -> str:
|
|
35
|
-
filename: str = get_filename(
|
|
36
|
-
inspection=inspection,
|
|
37
|
-
)
|
|
38
|
-
filename = f"{filename}.{inspection.metadata.file_type}"
|
|
39
|
-
if type(inspection) in [Video, ThermalVideo]:
|
|
40
|
-
inspection_path = self._store_video(filename, inspection, mission)
|
|
41
|
-
else:
|
|
42
|
-
inspection_path = self._store_image(filename, inspection, mission)
|
|
43
|
-
return inspection_path
|
|
44
|
-
|
|
45
|
-
def _store_image(
|
|
46
|
-
self, filename: str, inspection: Inspection, mission: Mission
|
|
47
|
-
) -> str:
|
|
48
|
-
multiform_body: MultipartEncoder = self._construct_multiform_request_image(
|
|
49
|
-
filename=filename, inspection=inspection, mission=mission
|
|
50
|
-
)
|
|
51
|
-
request_url: str = f"{self.url}/UploadSingleImage"
|
|
52
|
-
inspection_path = self._ingest(
|
|
53
|
-
inspection=inspection,
|
|
54
|
-
multiform_body=multiform_body,
|
|
55
|
-
request_url=request_url,
|
|
56
|
-
)
|
|
57
|
-
return inspection_path
|
|
58
|
-
|
|
59
|
-
def _store_video(
|
|
60
|
-
self, filename: str, inspection: Inspection, mission: Mission
|
|
61
|
-
) -> str:
|
|
62
|
-
multiform_body: MultipartEncoder = self._construct_multiform_request_video(
|
|
63
|
-
filename=filename, inspection=inspection, mission=mission
|
|
64
|
-
)
|
|
65
|
-
request_url = f"{self.url}/UploadSingleVideo"
|
|
66
|
-
inspection_path = self._ingest(
|
|
67
|
-
inspection=inspection,
|
|
68
|
-
multiform_body=multiform_body,
|
|
69
|
-
request_url=request_url,
|
|
70
|
-
)
|
|
71
|
-
return inspection_path
|
|
72
|
-
|
|
73
|
-
def _ingest(
|
|
74
|
-
self, inspection: Inspection, multiform_body: MultipartEncoder, request_url: str
|
|
75
|
-
) -> str:
|
|
76
|
-
token: str = self.credentials.get_token(self.request_scope).token
|
|
77
|
-
try:
|
|
78
|
-
response = self.request_handler.post(
|
|
79
|
-
url=request_url,
|
|
80
|
-
data=multiform_body,
|
|
81
|
-
headers={
|
|
82
|
-
"Authorization": f"Bearer {token}",
|
|
83
|
-
"Content-Type": multiform_body.content_type,
|
|
84
|
-
},
|
|
85
|
-
)
|
|
86
|
-
guid = json.loads(response.text)["guid"]
|
|
87
|
-
self.logger.info(f"SLIMM upload GUID: {guid}")
|
|
88
|
-
except (RequestException, HTTPError) as e:
|
|
89
|
-
self.logger.warning(
|
|
90
|
-
f"Failed to upload inspection: {inspection.id} to SLIMM due to a "
|
|
91
|
-
f"request exception"
|
|
92
|
-
)
|
|
93
|
-
raise StorageException from e
|
|
94
|
-
data = json.loads(response.content)
|
|
95
|
-
return data["guid"]
|
|
96
|
-
|
|
97
|
-
@staticmethod
|
|
98
|
-
def _construct_multiform_request_image(
|
|
99
|
-
filename: str, inspection: Inspection, mission: Mission
|
|
100
|
-
):
|
|
101
|
-
array_of_orientation = (
|
|
102
|
-
inspection.metadata.pose.orientation.to_quat_array().tolist()
|
|
103
|
-
)
|
|
104
|
-
multiform_body: MultipartEncoder = MultipartEncoder(
|
|
105
|
-
fields={
|
|
106
|
-
"PlantFacilitySAPCode": settings.PLANT_CODE,
|
|
107
|
-
"InstCode": settings.PLANT_SHORT_NAME,
|
|
108
|
-
"InternalClassification": settings.DATA_CLASSIFICATION,
|
|
109
|
-
"IsoCountryCode": "NO",
|
|
110
|
-
"Geodetic.CoordinateReferenceSystemCode": settings.COORDINATE_REFERENCE_SYSTEM, # noqa: E501
|
|
111
|
-
"Geodetic.VerticalCoordinateReferenceSystemCode": settings.VERTICAL_REFERENCE_SYSTEM, # noqa: E501
|
|
112
|
-
"Geodetic.OrientationReferenceSystem": settings.MEDIA_ORIENTATION_REFERENCE_SYSTEM, # noqa: E501
|
|
113
|
-
"SensorCarrier.SensorCarrierId": settings.ISAR_ID,
|
|
114
|
-
"SensorCarrier.ModelName": settings.ROBOT_TYPE,
|
|
115
|
-
"Mission.MissionId": mission.id,
|
|
116
|
-
"Mission.Client": "Equinor",
|
|
117
|
-
"ImageMetadata.Timestamp": inspection.metadata.start_time.isoformat(), # noqa: E501
|
|
118
|
-
"ImageMetadata.X": str(inspection.metadata.pose.position.x),
|
|
119
|
-
"ImageMetadata.Y": str(inspection.metadata.pose.position.y),
|
|
120
|
-
"ImageMetadata.Z": str(inspection.metadata.pose.position.z),
|
|
121
|
-
"ImageMetadata.CameraOrientation1": str(array_of_orientation[0]),
|
|
122
|
-
"ImageMetadata.CameraOrientation2": str(array_of_orientation[1]),
|
|
123
|
-
"ImageMetadata.CameraOrientation3": str(array_of_orientation[2]),
|
|
124
|
-
"ImageMetadata.CameraOrientation4": str(array_of_orientation[3]),
|
|
125
|
-
"ImageMetadata.AnalysisMethods": inspection.metadata.analysis
|
|
126
|
-
if inspection.metadata.analysis
|
|
127
|
-
else "N/A",
|
|
128
|
-
"ImageMetadata.Description": str(inspection.metadata.additional),
|
|
129
|
-
"ImageMetadata.FunctionalLocation": inspection.metadata.tag_id # noqa: E501
|
|
130
|
-
if inspection.metadata.tag_id
|
|
131
|
-
else "N/A",
|
|
132
|
-
"Filename": filename,
|
|
133
|
-
"AttachedFile": (filename, inspection.data),
|
|
134
|
-
}
|
|
135
|
-
)
|
|
136
|
-
return multiform_body
|
|
137
|
-
|
|
138
|
-
@staticmethod
|
|
139
|
-
def _construct_multiform_request_video(
|
|
140
|
-
filename: str,
|
|
141
|
-
inspection: Inspection,
|
|
142
|
-
mission: Mission,
|
|
143
|
-
):
|
|
144
|
-
array_of_orientation = (
|
|
145
|
-
inspection.metadata.pose.orientation.to_quat_array().tolist()
|
|
146
|
-
)
|
|
147
|
-
multiform_body: MultipartEncoder = MultipartEncoder(
|
|
148
|
-
fields={
|
|
149
|
-
"PlantFacilitySAPCode": settings.PLANT_CODE,
|
|
150
|
-
"InstCode": settings.PLANT_SHORT_NAME,
|
|
151
|
-
"InternalClassification": settings.DATA_CLASSIFICATION,
|
|
152
|
-
"IsoCountryCode": "NO",
|
|
153
|
-
"Geodetic.CoordinateReferenceSystemCode": settings.COORDINATE_REFERENCE_SYSTEM, # noqa: E501
|
|
154
|
-
"Geodetic.VerticalCoordinateReferenceSystemCode": settings.VERTICAL_REFERENCE_SYSTEM, # noqa: E501
|
|
155
|
-
"Geodetic.OrientationReferenceSystem": settings.MEDIA_ORIENTATION_REFERENCE_SYSTEM, # noqa: E501
|
|
156
|
-
"SensorCarrier.SensorCarrierId": settings.ISAR_ID,
|
|
157
|
-
"SensorCarrier.ModelName": settings.ROBOT_TYPE,
|
|
158
|
-
"Mission.MissionId": mission.id,
|
|
159
|
-
"Mission.Client": "Equinor",
|
|
160
|
-
"VideoMetadata.Timestamp": inspection.metadata.start_time.isoformat(), # noqa: E501
|
|
161
|
-
# Converting to int because SLIMM expects an int, while we use floats in operations.
|
|
162
|
-
"VideoMetadata.Duration": str(int(inspection.metadata.duration)), # type: ignore
|
|
163
|
-
"VideoMetadata.X": str(inspection.metadata.pose.position.x),
|
|
164
|
-
"VideoMetadata.Y": str(inspection.metadata.pose.position.y),
|
|
165
|
-
"VideoMetadata.Z": str(inspection.metadata.pose.position.z),
|
|
166
|
-
"VideoMetadata.CameraOrientation1": str(array_of_orientation[0]),
|
|
167
|
-
"VideoMetadata.CameraOrientation2": str(array_of_orientation[1]),
|
|
168
|
-
"VideoMetadata.CameraOrientation3": str(array_of_orientation[2]),
|
|
169
|
-
"VideoMetadata.CameraOrientation4": str(array_of_orientation[3]),
|
|
170
|
-
"VideoMetadata.AnalysisMethods": inspection.metadata.analysis
|
|
171
|
-
if inspection.metadata.analysis
|
|
172
|
-
else "N/A",
|
|
173
|
-
"VideoMetadata.Description": str(inspection.metadata.additional),
|
|
174
|
-
"VideoMetadata.FunctionalLocation": inspection.metadata.tag_id # noqa: E501
|
|
175
|
-
if inspection.metadata.tag_id
|
|
176
|
-
else "N/A",
|
|
177
|
-
"Filename": filename,
|
|
178
|
-
"AttachedFile": (filename, inspection.data),
|
|
179
|
-
}
|
|
180
|
-
)
|
|
181
|
-
return multiform_body
|
isar-1.15.0.dist-info/METADATA
DELETED
|
@@ -1,417 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: isar
|
|
3
|
-
Version: 1.15.0
|
|
4
|
-
Summary: Integration and Supervisory control of Autonomous Robots
|
|
5
|
-
Home-page: https://github.com/equinor/isar
|
|
6
|
-
Author: Equinor ASA
|
|
7
|
-
Author-email: fg_robots_dev@equinor.com
|
|
8
|
-
Classifier: Environment :: Other Environment
|
|
9
|
-
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: Programming Language :: Python
|
|
12
|
-
Classifier: Topic :: Scientific/Engineering
|
|
13
|
-
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
14
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
-
Requires-Python: >=3.8
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
Requires-Dist: alitra (>=1.1.0)
|
|
19
|
-
Requires-Dist: azure-identity
|
|
20
|
-
Requires-Dist: azure-keyvault-secrets
|
|
21
|
-
Requires-Dist: azure-storage-blob
|
|
22
|
-
Requires-Dist: backoff
|
|
23
|
-
Requires-Dist: click
|
|
24
|
-
Requires-Dist: dacite
|
|
25
|
-
Requires-Dist: fastapi-azure-auth
|
|
26
|
-
Requires-Dist: fastapi
|
|
27
|
-
Requires-Dist: injector
|
|
28
|
-
Requires-Dist: opencensus-ext-logging
|
|
29
|
-
Requires-Dist: opencensus-ext-requests
|
|
30
|
-
Requires-Dist: opencensus-ext-azure
|
|
31
|
-
Requires-Dist: numpy
|
|
32
|
-
Requires-Dist: paho-mqtt
|
|
33
|
-
Requires-Dist: pydantic
|
|
34
|
-
Requires-Dist: PyJWT
|
|
35
|
-
Requires-Dist: python-dotenv
|
|
36
|
-
Requires-Dist: PyYAML
|
|
37
|
-
Requires-Dist: requests-toolbelt
|
|
38
|
-
Requires-Dist: requests
|
|
39
|
-
Requires-Dist: transitions
|
|
40
|
-
Requires-Dist: uvicorn
|
|
41
|
-
Provides-Extra: dev
|
|
42
|
-
Requires-Dist: black ; extra == 'dev'
|
|
43
|
-
Requires-Dist: flake8 ; extra == 'dev'
|
|
44
|
-
Requires-Dist: mypy ; extra == 'dev'
|
|
45
|
-
Requires-Dist: myst-parser ; extra == 'dev'
|
|
46
|
-
Requires-Dist: pre-commit ; extra == 'dev'
|
|
47
|
-
Requires-Dist: pytest-dotenv ; extra == 'dev'
|
|
48
|
-
Requires-Dist: pytest-mock ; extra == 'dev'
|
|
49
|
-
Requires-Dist: pytest-xdist ; extra == 'dev'
|
|
50
|
-
Requires-Dist: pytest ; extra == 'dev'
|
|
51
|
-
Requires-Dist: requests-mock ; extra == 'dev'
|
|
52
|
-
Requires-Dist: sphinx ; extra == 'dev'
|
|
53
|
-
|
|
54
|
-
# ISAR
|
|
55
|
-
|
|
56
|
-
[](https://github.com/equinor/isar/actions/workflows/pythonpackage.yml)
|
|
57
|
-
[](https://github.com/psf/black)
|
|
58
|
-
[](https://opensource.org/licenses/EPL-2.0)
|
|
59
|
-
|
|
60
|
-
ISAR - Integration and Supervisory control of Autonomous Robots - is a tool for integrating robot applications into
|
|
61
|
-
operator systems. Through the ISAR API you can send commands to a robot to do missions and collect results from the
|
|
62
|
-
missions.
|
|
63
|
-
|
|
64
|
-
## Getting started
|
|
65
|
-
|
|
66
|
-
Steps:
|
|
67
|
-
|
|
68
|
-
- Install
|
|
69
|
-
- Integrate a robot
|
|
70
|
-
- Run the ISAR server
|
|
71
|
-
- Run a robot mission
|
|
72
|
-
|
|
73
|
-
### Install
|
|
74
|
-
|
|
75
|
-
For local development, please fork the repository. Then, clone and install in the repository root folder:
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
git clone https://github.com/<path_to_parent>/isar
|
|
79
|
-
cd isar
|
|
80
|
-
pip install -e .[dev]
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
For `zsh` you might have to type `".[dev]"`
|
|
84
|
-
|
|
85
|
-
Verify that you can run the tests:
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
pytest .
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
The repository contains a configuration file for installing pre-commit hooks. Currently, [black](https://github.com/psf/black) and a mirror of [mypy](https://github.com/pre-commit/mirrors-mypy) are configured hooks. Install with:
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
pre-commit install
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Verify that pre-commit runs:
|
|
98
|
-
|
|
99
|
-
```
|
|
100
|
-
pre-commit
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
pre-commit will now run the installed hooks before code is commited to git. To turn pre-commit off, run:
|
|
104
|
-
|
|
105
|
-
```
|
|
106
|
-
pre-commit uninstall
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Robot integration
|
|
110
|
-
|
|
111
|
-
To connect the state machine to a robot in a separate repository, it is required that the separate repository implements
|
|
112
|
-
the [robot interface](https://github.com/equinor/isar/blob/main/src/robot_interface/robot_interface.py). A mocked robot
|
|
113
|
-
can be found in [this repository](https://github.com/equinor/isar-robot). Install the repo, i.e:
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
pip install isar-robot
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Then, ensure the `ISAR_ROBOT_PACKAGE` variable in [settings.env](./src/isar/config/settings.env)
|
|
120
|
-
is set to the name of the package you installed. `isar_robot` is set by default. See the section
|
|
121
|
-
for [configuration](#configuration) for overwriting configuration.
|
|
122
|
-
|
|
123
|
-
If you have the robot repository locally, you can simply install through
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
pip install -e /path/to/robot/repo/
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
#### Running ISAR with a robot simulator
|
|
130
|
-
|
|
131
|
-
A simulator based on the open source robot Turtlebot3 has been implemented for use with ISAR and may be
|
|
132
|
-
found [here](https://github.com/equinor/isar-turtlebot). Follow the installation instructions for the simulator and
|
|
133
|
-
install `isar-turtlebot` in the same manner as given in the [robot integration](#robot-integration) section. Overwrite
|
|
134
|
-
the following configuration variables:
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
ISAR_ROBOT_PACKAGE = isar_turtlebot
|
|
138
|
-
ISAR_DEFAULT_MAP = turtleworld
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Run ISAR server
|
|
142
|
-
|
|
143
|
-
To run ISAR:
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
python main.py
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Note, running the full system requires that an implementation of a robot has been installed. See
|
|
150
|
-
this [section](#robot-integration) for installing a mocked robot or a Turtlebot3 simulator.
|
|
151
|
-
|
|
152
|
-
### Running a robot mission
|
|
153
|
-
|
|
154
|
-
Once the application has been started the swagger site may be accessed at
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
http://localhost:3000/docs
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Execute the `/schedule/start-mission` endpoint with `mission_id=1` to run a mission.
|
|
161
|
-
|
|
162
|
-
In [this](./src/isar/config/predefined_missions) folder there are predefined default missions, for example the mission
|
|
163
|
-
corresponding to `mission_id=1`. A new mission may be added by adding a new json-file with a mission description. Note,
|
|
164
|
-
the mission IDs must be unique.
|
|
165
|
-
|
|
166
|
-
### Running with docker-compose
|
|
167
|
-
|
|
168
|
-
ISAR may be started with an instance of the [isar-robot](https://github.com/equinor/isar-robot) package by
|
|
169
|
-
|
|
170
|
-
```shell
|
|
171
|
-
docker-compose up --build
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
Provided that the simulator from [isar-turtlebot](https://github.com/equinor/isar-turtlebot) is running ISAR may be
|
|
175
|
-
started with the turtlebot by
|
|
176
|
-
|
|
177
|
-
```shell
|
|
178
|
-
docker-compose -f docker-compose-turtlebot.yml up --build
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### Configuration
|
|
182
|
-
|
|
183
|
-
The system consists of many configuration variables which may alter the functionality. As an example, it is possible to
|
|
184
|
-
change mission planners or add multiple storage handlers as described in the [mission planner](#mission-planner)
|
|
185
|
-
and [storage](#storage) sections.
|
|
186
|
-
|
|
187
|
-
There are two methods of specifying configuration.
|
|
188
|
-
|
|
189
|
-
1. Override the default value by setting an environment variable.
|
|
190
|
-
|
|
191
|
-
Every configuration variable is defined in [settings.py](./src/isar/config/settings.py), and they may all be
|
|
192
|
-
overwritten by specifying the variables in the environment instead. Note that the configuration variable must be
|
|
193
|
-
prefixed with `ISAR_` when specified in the environment. So for the `ROBOT_PACKAGE`configuration variable:
|
|
194
|
-
|
|
195
|
-
```shell
|
|
196
|
-
export ISAR_ROBOT_PACKAGE=isar_turtlebot
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
This means ISAR will connect to `isar_turtlebot` robot package.
|
|
200
|
-
|
|
201
|
-
2. Adding environment variables through [settings.env](./src/isar/config/settings.env).
|
|
202
|
-
|
|
203
|
-
By adding environment variables with the prefix `ISAR_` to the [settings.env](./src/isar/config/settings.env) file
|
|
204
|
-
the configuration variables will be overwritten by the values in this file.
|
|
205
|
-
|
|
206
|
-
### Running tests
|
|
207
|
-
|
|
208
|
-
After following the steps in [Development](#install), you can run the tests:
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
pytest .
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
To create an interface test in your robot repository, use the function `interface_test` from `robot_interface`. The
|
|
215
|
-
argument should be an interface object from your robot specific implementation.
|
|
216
|
-
See [isar-robot](https://github.com/equinor/isar-robot/blob/main/tests/interfaces/test_robotinterface.py) for example.
|
|
217
|
-
|
|
218
|
-
#### Integration tests
|
|
219
|
-
|
|
220
|
-
Integration tests can be found [here](https://github.com/equinor/isar/tree/main/tests/integration) and have been created
|
|
221
|
-
with a simulator in mind. The integration tests will not run as part of `pytest .` or as part of the CI/CD pipeline. To
|
|
222
|
-
run the integration tests please follow the instructions in [this section](#running-isar-with-a-robot-simulator) for
|
|
223
|
-
setting up the `isar-turtlebot` implementation with simulator and run the following command once the simulation has been
|
|
224
|
-
launched.
|
|
225
|
-
|
|
226
|
-
```bash
|
|
227
|
-
pytest tests/integration
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Note that these tests will run towards the actual simulation (you may monitor it through Gazebo and RVIZ) and it will
|
|
231
|
-
take a long time.
|
|
232
|
-
|
|
233
|
-
### Documentation
|
|
234
|
-
|
|
235
|
-
To build the project documentation, run the following commands:
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
cd docs
|
|
239
|
-
make docs
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
The documentation can now be viewed at `docs/build/html/index.html`.
|
|
243
|
-
|
|
244
|
-
### Contributing
|
|
245
|
-
|
|
246
|
-
We welcome all kinds of contributions, including code, bug reports, issues, feature requests, and documentation. The
|
|
247
|
-
preferred way of submitting a contribution is to either make an [issue](https://github.com/equinor/isar/issues) on
|
|
248
|
-
GitHub or by forking the project on GitHub and making a pull requests.
|
|
249
|
-
|
|
250
|
-
## Components
|
|
251
|
-
|
|
252
|
-
The system consists of two main components.
|
|
253
|
-
|
|
254
|
-
1. State machine
|
|
255
|
-
1. FastAPI
|
|
256
|
-
|
|
257
|
-
### State machine
|
|
258
|
-
|
|
259
|
-
The state machine handles interaction with the robots API and monitors the execution of missions. It also enables
|
|
260
|
-
interacting with the robot before, during and after missions.
|
|
261
|
-
|
|
262
|
-
The state machine is based on the [transitions](https://github.com/pytransitions/transitions) package for Python. An visualization of the state machine can be seen below:
|
|
263
|
-

|
|
264
|
-
|
|
265
|
-
In general the states
|
|
266
|
-
|
|
267
|
-
```
|
|
268
|
-
States.Off,
|
|
269
|
-
States.Initialize,
|
|
270
|
-
States.Initiate,
|
|
271
|
-
States.Stop,
|
|
272
|
-
States.Monitor,
|
|
273
|
-
States.Paused,
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
indicates that the state machine is already running. For running a mission the state machine need to be in the state
|
|
277
|
-
|
|
278
|
-
```
|
|
279
|
-
States.Idle
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
### FastAPI
|
|
283
|
-
|
|
284
|
-
The FastAPI establishes an interface to the state machine for the user. As the API and state machine are separate
|
|
285
|
-
threads, they communicate through python queues. FastAPI runs on an ASGI-server, specifically uvicorn. The
|
|
286
|
-
FastAPI-framework is split into routers where the endpoint operations are defined.
|
|
287
|
-
|
|
288
|
-
## Mission planner
|
|
289
|
-
|
|
290
|
-
The mission planner that is currently in use is a local mission planner, where missions are specified in a json file. You can create your own mission planner by implementing
|
|
291
|
-
the [mission planner interface](./src/isar/mission_planner/mission_planner_interface.py) and adding your planner to the
|
|
292
|
-
selection [here](./src/isar/modules.py). Note that you must add your module as an option in the dictionary.
|
|
293
|
-
|
|
294
|
-
## Storage
|
|
295
|
-
|
|
296
|
-
The storage modules that are used is defined by the `ISAR_STORAGE` configuration variable. This can be changed by
|
|
297
|
-
overriding the configuration through an environment variable. It accepts a json encoded list and will use each element
|
|
298
|
-
in the list to retrieve the corresponding handler. The current options are
|
|
299
|
-
|
|
300
|
-
```
|
|
301
|
-
ISAR_STORAGE = '["local", "blob", "slimm"]'
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
Note that the `blob` and `slimm` options require special configuration to authenticate to these endpoints.
|
|
305
|
-
|
|
306
|
-
### Implement your own storage module
|
|
307
|
-
|
|
308
|
-
You can create your own storage module by implementing the [storage interface](./src/isar/storage/storage_interface.py)
|
|
309
|
-
and adding your storage module to the selection [here](./src/isar/modules.py). Note that you must add your module as an
|
|
310
|
-
option in the dictionary.
|
|
311
|
-
|
|
312
|
-
## Task selection
|
|
313
|
-
|
|
314
|
-
The tasks of a mission are selected based on a task selector module, defined by the `TASK_SELECTOR` configuration variable. The default task selector is `sequential`. When using the default module, tasks are executed in sequential order defined by the current input mission.
|
|
315
|
-
|
|
316
|
-
### Implement you own task selector module
|
|
317
|
-
|
|
318
|
-
Custom task selector modules may be added by implementing additional versions of the [task selector interface](./src/isar/mission_planner/task_selector_interface.py).
|
|
319
|
-
|
|
320
|
-
For every custom module, the interface function `next_task()` must be implemented. All interface implementations by default have access to the list of tasks in the current mission through the member `self.tasks`, however additional variables may be supplied by adding arguments to `next_task()`. To comply with the interface definition, the function should return the next task upon every call, and raise the `TaskSelectorStop` exception when all tasks in the current mission have been completed:
|
|
321
|
-
|
|
322
|
-
```python
|
|
323
|
-
class CustomTaskSelector(TaskSelectorInterface):
|
|
324
|
-
...
|
|
325
|
-
def next_task(...) -> Task:
|
|
326
|
-
|
|
327
|
-
# Add code here
|
|
328
|
-
...
|
|
329
|
-
|
|
330
|
-
# Raise `TaskSelectorStop` when all tasks have been completed
|
|
331
|
-
...
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
Optionally, the `initialize()` function may be extended by supplementing the parameter list or function body:
|
|
335
|
-
|
|
336
|
-
```python
|
|
337
|
-
class CustomTaskSelector(TaskSelectorInterface):
|
|
338
|
-
...
|
|
339
|
-
def initialize(self, tasks: List[Task], ...) -> None:
|
|
340
|
-
super.initialize(tasks=tasks)
|
|
341
|
-
|
|
342
|
-
# Add supplementary code here
|
|
343
|
-
...
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
A custom task selector may be made available during [module selection](./src/isar/modules.py) by adding it to the series of options in the dictionary of injector modules. It can then be activated by overriding the task selector configuration variable:
|
|
347
|
-
|
|
348
|
-
```python
|
|
349
|
-
# Add custom task selector module to `modules.py`
|
|
350
|
-
|
|
351
|
-
class CustomTaskSelectorModule(Module):
|
|
352
|
-
@provider
|
|
353
|
-
@singleton
|
|
354
|
-
def provide_task_selector(self) -> TaskSelectorInterface:
|
|
355
|
-
return CustomTaskSelector()
|
|
356
|
-
|
|
357
|
-
...
|
|
358
|
-
|
|
359
|
-
# Make it available to select during injector instantiation
|
|
360
|
-
|
|
361
|
-
modules: dict[str, tuple[Module, Union[str, bool]]] = {
|
|
362
|
-
...
|
|
363
|
-
"task_selector": (
|
|
364
|
-
{
|
|
365
|
-
"sequential": SequentialTaskSelectorModule,
|
|
366
|
-
"custom": CustomTaskSelectorModule
|
|
367
|
-
}
|
|
368
|
-
...
|
|
369
|
-
)
|
|
370
|
-
...
|
|
371
|
-
}
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
## API authentication
|
|
375
|
-
|
|
376
|
-
The API has an option to include user authentication. This can be enabled by setting the environment variable
|
|
377
|
-
|
|
378
|
-
```
|
|
379
|
-
ISAR_AUTHENTICATION_ENABLED = true
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
By default, the `local` storage module is used and API authentication is disabled. If using Azure Blob Storage a set of
|
|
383
|
-
environment variables must be available which gives access to an app registration that may use the storage account.
|
|
384
|
-
Enabling API authentication also requires the same environment variables. The required variables are
|
|
385
|
-
|
|
386
|
-
```
|
|
387
|
-
AZURE_CLIENT_ID
|
|
388
|
-
AZURE_TENANT_ID
|
|
389
|
-
AZURE_CLIENT_SECRET
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
## MQTT communication
|
|
393
|
-
|
|
394
|
-
ISAR is able to publish parts of its internal state to topics on an MQTT broker whenever they change. This is by default
|
|
395
|
-
turned off but may be activated by setting the environment variable
|
|
396
|
-
|
|
397
|
-
```
|
|
398
|
-
ISAR_MQTT_ENABLED = true
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
The connection to the broker will be determined by the following configuration values in `settings.py`
|
|
402
|
-
|
|
403
|
-
```
|
|
404
|
-
ISAR_MQTT_USERNAME
|
|
405
|
-
ISAR_MQTT_HOST
|
|
406
|
-
ISAR_MQTT_PORT
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
The default values of these are overwritten by the environment in `settings.env`.
|
|
410
|
-
|
|
411
|
-
To specify broker password, add the following environment variable to a .env file in the root of the repository:
|
|
412
|
-
|
|
413
|
-
```
|
|
414
|
-
ISAR_MQTT_PASSWORD
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
If not specified the password will default to an empty string.
|