isar 1.20.2__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/apis/api.py +148 -76
- isar/apis/models/__init__.py +0 -1
- isar/apis/models/models.py +21 -11
- isar/apis/models/start_mission_definition.py +110 -168
- isar/apis/robot_control/robot_controller.py +41 -0
- isar/apis/schedule/scheduling_controller.py +124 -162
- isar/apis/security/authentication.py +5 -5
- isar/config/certs/ca-cert.pem +33 -31
- isar/config/keyvault/keyvault_service.py +1 -1
- isar/config/log.py +45 -40
- isar/config/logging.conf +16 -31
- isar/config/open_telemetry.py +102 -0
- isar/config/predefined_mission_definition/default_exr.json +0 -2
- 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 +76 -111
- 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 -199
- 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 +57 -40
- isar/services/service_connections/mqtt/mqtt_client.py +47 -11
- isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
- isar/services/service_connections/mqtt/robot_info_publisher.py +3 -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 +219 -538
- isar/state_machine/states/__init__.py +0 -8
- 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 -247
- isar/state_machine/states/offline.py +51 -53
- 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 +21 -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 +70 -52
- isar/storage/local_storage.py +25 -12
- isar/storage/storage_interface.py +28 -7
- isar/storage/uploader.py +174 -55
- isar/storage/utilities.py +32 -29
- {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/METADATA +73 -110
- isar-1.34.9.dist-info/RECORD +135 -0
- {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
- {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/entry_points.txt +1 -0
- robot_interface/models/exceptions/robot_exceptions.py +91 -41
- robot_interface/models/initialize/__init__.py +0 -1
- robot_interface/models/inspection/__init__.py +0 -13
- robot_interface/models/inspection/inspection.py +42 -33
- robot_interface/models/mission/mission.py +14 -15
- robot_interface/models/mission/status.py +20 -26
- robot_interface/models/mission/task.py +154 -121
- 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 +119 -84
- robot_interface/telemetry/mqtt_client.py +74 -12
- robot_interface/telemetry/payloads.py +91 -13
- 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 -25
- 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/stid/__init__.py +0 -0
- isar/services/utilities/queue_utilities.py +0 -39
- isar/state_machine/states/idle.py +0 -85
- isar/state_machine/states/initialize.py +0 -71
- isar/state_machine/states/initiate.py +0 -142
- isar/state_machine/states/off.py +0 -18
- isar/state_machine/states/stop.py +0 -95
- isar/storage/slimm_storage.py +0 -191
- isar-1.20.2.dist-info/RECORD +0 -116
- robot_interface/models/initialize/initialize_params.py +0 -9
- robot_interface/models/mission/step.py +0 -234
- {isar-1.20.2.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
- {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
isar/config/settings.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from importlib.resources import as_file, files
|
|
3
|
-
from typing import Any, List
|
|
3
|
+
from typing import Any, List
|
|
4
4
|
|
|
5
5
|
from dotenv import load_dotenv
|
|
6
6
|
from pydantic import Field, ValidationInfo, field_validator
|
|
@@ -8,26 +8,21 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
8
8
|
|
|
9
9
|
from isar.config import predefined_missions
|
|
10
10
|
from robot_interface.models.robots.robot_model import RobotModel
|
|
11
|
-
from robot_interface.telemetry.payloads import
|
|
11
|
+
from robot_interface.telemetry.payloads import DocumentInfo
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class Settings(BaseSettings):
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
env_file = None
|
|
22
|
-
super().__init__(_env_file=env_file)
|
|
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
|
+
|
|
18
|
+
# Connection string for Azure Application Insights
|
|
19
|
+
# This is optional and it will use managed identity if not set
|
|
20
|
+
APPLICATIONINSIGHTS_CONNECTION_STRING: str = Field(default="")
|
|
23
21
|
|
|
24
22
|
# Determines which robot package ISAR will attempt to import
|
|
25
23
|
# Name must match with an installed python package in the local environment
|
|
26
24
|
ROBOT_PACKAGE: str = Field(default="isar_robot")
|
|
27
25
|
|
|
28
|
-
# The run mode of the robot (stepwise or full mission)
|
|
29
|
-
RUN_MISSION_STEPWISE: bool = Field(default=True)
|
|
30
|
-
|
|
31
26
|
# Determines the local path in which results from missions are stored
|
|
32
27
|
LOCAL_STORAGE_PATH: str = Field(default="./results")
|
|
33
28
|
|
|
@@ -48,20 +43,21 @@ class Settings(BaseSettings):
|
|
|
48
43
|
# Name of default map transformation
|
|
49
44
|
DEFAULT_MAP: str = Field(default="default_map")
|
|
50
45
|
|
|
51
|
-
# Location of JSON files containing predefined maps
|
|
52
|
-
MAPS_FOLDER: str = Field(default="src/isar/config/maps/")
|
|
53
|
-
|
|
54
46
|
# Determines the number of state transitions that are kept in the log
|
|
55
47
|
STATE_TRANSITIONS_LOG_LENGTH: int = Field(default=20)
|
|
56
48
|
|
|
57
|
-
# Number of attempts to
|
|
58
|
-
INITIATE_FAILURE_COUNTER_LIMIT: int = Field(default=10)
|
|
59
|
-
|
|
60
|
-
# Number of attempts to request a step status in monitor before cancelling
|
|
49
|
+
# Number of attempts to request a task status in monitor before cancelling
|
|
61
50
|
REQUEST_STATUS_FAILURE_COUNTER_LIMIT: int = Field(default=3)
|
|
62
51
|
|
|
52
|
+
# Time allocated to reconnect when failing to retrieve status due to communication
|
|
53
|
+
# issues
|
|
54
|
+
REQUEST_STATUS_COMMUNICATION_RECONNECT_DELAY: float = Field(default=10)
|
|
55
|
+
|
|
56
|
+
# Number of attempts for state transitions resume and pause if failed
|
|
57
|
+
STATE_TRANSITION_NUM_RETIRES: int = Field(default=10)
|
|
58
|
+
|
|
63
59
|
# Number of attempts to stop the robot before giving up
|
|
64
|
-
STOP_ROBOT_ATTEMPTS_LIMIT: int = Field(default=
|
|
60
|
+
STOP_ROBOT_ATTEMPTS_LIMIT: int = Field(default=3)
|
|
65
61
|
|
|
66
62
|
# Number of attempts to stop the robot before giving up
|
|
67
63
|
UPLOAD_FAILURE_ATTEMPTS_LIMIT: int = Field(default=10)
|
|
@@ -70,10 +66,20 @@ class Settings(BaseSettings):
|
|
|
70
66
|
UPLOAD_FAILURE_MAX_WAIT: int = Field(default=60)
|
|
71
67
|
|
|
72
68
|
# ISAR telemetry intervals
|
|
73
|
-
ROBOT_STATUS_PUBLISH_INTERVAL: float = Field(default=1)
|
|
74
69
|
ROBOT_HEARTBEAT_PUBLISH_INTERVAL: float = Field(default=1)
|
|
75
70
|
ROBOT_INFO_PUBLISH_INTERVAL: float = Field(default=5)
|
|
71
|
+
ROBOT_API_BATTERY_POLL_INTERVAL: float = Field(default=5)
|
|
76
72
|
ROBOT_API_STATUS_POLL_INTERVAL: float = Field(default=5)
|
|
73
|
+
THREAD_CHECK_INTERVAL: float = Field(default=0.01)
|
|
74
|
+
|
|
75
|
+
# Determines the minimum battery level the robot must have to start a mission
|
|
76
|
+
# If it drops below this level it will recharge to the value set by
|
|
77
|
+
# ROBOT_BATTERY_RECHARGE_THRESHOLD before starting new missions
|
|
78
|
+
ROBOT_MISSION_BATTERY_START_THRESHOLD: float = Field(default=25.0)
|
|
79
|
+
|
|
80
|
+
# Determines the minimum battery threshold to consider the robot recharged
|
|
81
|
+
# and ready for more missions, after having run low on charge
|
|
82
|
+
ROBOT_BATTERY_RECHARGE_THRESHOLD: float = Field(default=80.0)
|
|
77
83
|
|
|
78
84
|
# FastAPI host
|
|
79
85
|
API_HOST_VIEWED_EXTERNALLY: str = Field(default="0.0.0.0")
|
|
@@ -81,20 +87,21 @@ class Settings(BaseSettings):
|
|
|
81
87
|
# FastAPI port
|
|
82
88
|
API_PORT: int = Field(default=3000)
|
|
83
89
|
|
|
90
|
+
# Determines how long delay time should be allowed before returning home
|
|
91
|
+
RETURN_HOME_DELAY: int = Field(default=10)
|
|
92
|
+
|
|
93
|
+
# Sets how many times the robot should try to return home if a return home fails
|
|
94
|
+
RETURN_HOME_RETRY_LIMIT: int = Field(default=5)
|
|
95
|
+
|
|
84
96
|
# Determines which mission planner module is used by ISAR
|
|
85
97
|
MISSION_PLANNER: str = Field(default="local")
|
|
86
98
|
|
|
87
|
-
# Determines which task selector module is used by ISAR
|
|
88
|
-
# Options: [sequential]
|
|
89
|
-
TASK_SELECTOR: str = Field(default="sequential")
|
|
90
|
-
|
|
91
99
|
# Determines which storage modules are used by ISAR
|
|
92
100
|
# Multiple storage modules can be chosen
|
|
93
101
|
# Each module will be called when storing results from inspections
|
|
94
102
|
# Selecting a different storage module than local may require certain access rights
|
|
95
103
|
STORAGE_LOCAL_ENABLED: bool = Field(default=True)
|
|
96
104
|
STORAGE_BLOB_ENABLED: bool = Field(default=False)
|
|
97
|
-
STORAGE_SLIMM_ENABLED: bool = Field(default=False)
|
|
98
105
|
|
|
99
106
|
# Determines whether the MQTT publishing module should be enabled or not
|
|
100
107
|
# The publishing module will attempt to connect to the MQTT broker configured in
|
|
@@ -110,7 +117,7 @@ class Settings(BaseSettings):
|
|
|
110
117
|
# Enabling this requires certain resources available for OAuth2 authentication
|
|
111
118
|
# Currently supported authentication is Azure AD
|
|
112
119
|
# (https://github.com/Intility/fastapi-azure-auth)
|
|
113
|
-
AUTHENTICATION_ENABLED: bool = Field(default=
|
|
120
|
+
AUTHENTICATION_ENABLED: bool = Field(default=True)
|
|
114
121
|
|
|
115
122
|
# Tenant ID for the Azure tenant with your Azure Active Directory application
|
|
116
123
|
AZURE_TENANT_ID: str = Field(default="3aa4a235-b6e2-48d5-9195-7fcf05b459b0")
|
|
@@ -140,46 +147,28 @@ class Settings(BaseSettings):
|
|
|
140
147
|
# Keyvault name
|
|
141
148
|
KEYVAULT_NAME: str = Field(default="IsarDevKv")
|
|
142
149
|
|
|
150
|
+
# Determines whether inspections are uploaded asynchronously or get_inspections in robotinterface
|
|
151
|
+
UPLOAD_INSPECTIONS_ASYNC: bool = Field(default=False)
|
|
152
|
+
|
|
143
153
|
# URL to storage account for Azure Blob Storage
|
|
144
|
-
|
|
145
|
-
default="https://eqrobotdevstorage.blob.core.windows.net"
|
|
146
|
-
)
|
|
154
|
+
BLOB_STORAGE_ACCOUNT: str = Field(default="")
|
|
147
155
|
|
|
148
156
|
# Name of blob container in Azure Blob Storage [slimm test]
|
|
149
157
|
BLOB_CONTAINER: str = Field(default="test")
|
|
150
158
|
|
|
151
|
-
|
|
152
|
-
SLIMM_CLIENT_ID: str = Field(default="c630ca4d-d8d6-45ab-8cc6-68a363d0de9e")
|
|
153
|
-
|
|
154
|
-
# Scope for access to SLIMM Ingestion API
|
|
155
|
-
SLIMM_APP_SCOPE: str = Field(default=".default")
|
|
156
|
-
|
|
157
|
-
# URL for SLIMM endpoint
|
|
158
|
-
SLIMM_API_URL: str = Field(
|
|
159
|
-
default="https://scinspectioningestapitest.azurewebsites.net/Ingest"
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
# Whether the results should be copied directly into the SLIMM datalake or only the
|
|
163
|
-
# metadata
|
|
164
|
-
COPY_FILES_TO_SLIMM_DATALAKE: bool = Field(default=False)
|
|
159
|
+
PERSISTENT_STORAGE_CONNECTION_STRING: str = Field(default="")
|
|
165
160
|
|
|
166
161
|
# The configuration of this section is tightly coupled with the metadata that is
|
|
167
162
|
# submitted with the results once they have been uploaded.
|
|
168
163
|
|
|
169
164
|
# Four digit code indicating facility
|
|
170
|
-
PLANT_CODE: str = Field(default="
|
|
165
|
+
PLANT_CODE: str = Field(default="1210")
|
|
171
166
|
|
|
172
167
|
# Name of the facility the robot is operating in
|
|
173
|
-
PLANT_NAME: str = Field(default="
|
|
168
|
+
PLANT_NAME: str = Field(default="Huldra")
|
|
174
169
|
|
|
175
170
|
# Shortname of the facility the robot is operating in
|
|
176
|
-
PLANT_SHORT_NAME: str = Field(default="
|
|
177
|
-
|
|
178
|
-
# Country the robot is operating in
|
|
179
|
-
COUNTRY: str = Field(default="Norway")
|
|
180
|
-
|
|
181
|
-
# Type of robot ISAR is monitoring
|
|
182
|
-
ROBOT_TYPE: str = Field(default="robot")
|
|
171
|
+
PLANT_SHORT_NAME: str = Field(default="HUA")
|
|
183
172
|
|
|
184
173
|
# Name of robot
|
|
185
174
|
ROBOT_NAME: str = Field(default="Placebot")
|
|
@@ -190,25 +179,8 @@ class Settings(BaseSettings):
|
|
|
190
179
|
# Serial number of the robot ISAR is connected to
|
|
191
180
|
SERIAL_NUMBER: str = Field(default="0001")
|
|
192
181
|
|
|
193
|
-
#
|
|
194
|
-
|
|
195
|
-
default=[
|
|
196
|
-
VideoStream(
|
|
197
|
-
name="Front camera",
|
|
198
|
-
url="http://localhost:5000/videostream/front",
|
|
199
|
-
type="turtlebot",
|
|
200
|
-
),
|
|
201
|
-
VideoStream(
|
|
202
|
-
name="Rear camera",
|
|
203
|
-
url="http://localhost:5000/videostream/rear",
|
|
204
|
-
type="turtlebot",
|
|
205
|
-
),
|
|
206
|
-
]
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
# Data scheme the robot should adhere to
|
|
210
|
-
# Options [DS0001]
|
|
211
|
-
DATA_SCHEME: str = Field(default="DS0001")
|
|
182
|
+
# Info about robot documentation
|
|
183
|
+
DOCUMENTATION: List[DocumentInfo] = Field(default=[])
|
|
212
184
|
|
|
213
185
|
# Coordinate reference system of facility
|
|
214
186
|
COORDINATE_REFERENCE_SYSTEM: str = Field(default="EQUINOR:4100001")
|
|
@@ -220,9 +192,6 @@ class Settings(BaseSettings):
|
|
|
220
192
|
# Options [quaternion]
|
|
221
193
|
MEDIA_ORIENTATION_REFERENCE_SYSTEM: str = Field(default="quaternion")
|
|
222
194
|
|
|
223
|
-
# Contractor who is responsible for robot missions
|
|
224
|
-
CONTRACTOR: str = Field(default="equinor")
|
|
225
|
-
|
|
226
195
|
# Timezone
|
|
227
196
|
TIMEZONE: str = Field(default="UTC+00:00")
|
|
228
197
|
|
|
@@ -232,15 +201,29 @@ class Settings(BaseSettings):
|
|
|
232
201
|
# List of MQTT Topics
|
|
233
202
|
TOPIC_ISAR_STATUS: str = Field(default="status", validate_default=True)
|
|
234
203
|
TOPIC_ISAR_MISSION: str = Field(default="mission", validate_default=True)
|
|
204
|
+
TOPIC_ISAR_MISSION_ABORTED: str = Field(
|
|
205
|
+
default="aborted_mission", validate_default=True
|
|
206
|
+
)
|
|
235
207
|
TOPIC_ISAR_TASK: str = Field(default="task", validate_default=True)
|
|
236
|
-
TOPIC_ISAR_STEP: str = Field(default="step", validate_default=True)
|
|
237
208
|
TOPIC_ISAR_INSPECTION_RESULT: str = Field(
|
|
238
209
|
default="inspection_result", validate_default=True
|
|
239
210
|
)
|
|
211
|
+
TOPIC_ISAR_INSPECTION_VALUE: str = Field(
|
|
212
|
+
default="inspection_value", validate_default=True
|
|
213
|
+
)
|
|
240
214
|
TOPIC_ISAR_ROBOT_INFO: str = Field(default="robot_info", validate_default=True)
|
|
241
215
|
TOPIC_ISAR_ROBOT_HEARTBEAT: str = Field(
|
|
242
216
|
default="robot_heartbeat", validate_default=True
|
|
243
217
|
)
|
|
218
|
+
TOPIC_ISAR_STARTUP: str = Field(default="startup", validate_default=True)
|
|
219
|
+
TOPIC_ISAR_INTERVENTION_NEEDED: str = Field(
|
|
220
|
+
default="intervention_needed", validate_default=True
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# List of MQTT Topics Expiry
|
|
224
|
+
MQTT_ROBOT_HEARTBEAT_EXPIRY: int = Field(default=5)
|
|
225
|
+
MQTT_TELEMETRY_EXPIRY: int = Field(default=10)
|
|
226
|
+
MQTT_MISSION_AND_TASK_EXPIRY: int = Field(default=86400)
|
|
244
227
|
|
|
245
228
|
# Logging
|
|
246
229
|
|
|
@@ -250,47 +233,29 @@ class Settings(BaseSettings):
|
|
|
250
233
|
# Each handler will be called when logging
|
|
251
234
|
# Selecting a different log handler than local may require certain access rights:
|
|
252
235
|
# - The Azure AI logger requires the 'APPLICATIONINSIGHTS_CONNECTION_STRING' to be set as an environment variable.
|
|
236
|
+
# In general logging is configured in logging.conf
|
|
253
237
|
LOG_HANDLER_LOCAL_ENABLED: bool = Field(default=True)
|
|
254
238
|
LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED: bool = Field(default=False)
|
|
255
239
|
|
|
256
|
-
|
|
257
|
-
API_LOG_LEVEL: str = Field(default="INFO")
|
|
258
|
-
MAIN_LOG_LEVEL: str = Field(default="INFO")
|
|
259
|
-
MQTT_LOG_LEVEL: str = Field(default="INFO")
|
|
260
|
-
STATE_MACHINE_LOG_LEVEL: str = Field(default="INFO")
|
|
261
|
-
UPLOADER_LOG_LEVEL: str = Field(default="INFO")
|
|
262
|
-
CONSOLE_LOG_LEVEL: str = Field(default="INFO")
|
|
263
|
-
URLLIB3_LOG_LEVEL: str = Field(default="WARNING")
|
|
264
|
-
UVICORN_LOG_LEVEL: str = Field(default="WARNING")
|
|
265
|
-
AZURE_LOG_LEVEL: str = Field(default="WARNING")
|
|
240
|
+
DEBUG_LOG_FORMATTER: bool = Field(default=False)
|
|
266
241
|
|
|
267
|
-
|
|
242
|
+
# You can set logger levels from environment variables ending with _LOG_LEVEL
|
|
243
|
+
# For example, MQTT_LOG_LEVEL="DEBUG" will set the logger MQTT_LOGGER to DEBUG level
|
|
244
|
+
# Handeled in log.py and only work for loggers defined in logging.conf
|
|
268
245
|
|
|
269
246
|
REQUIRED_ROLE: str = Field(default="Mission.Control")
|
|
270
247
|
|
|
271
|
-
@field_validator("LOG_LEVELS")
|
|
272
|
-
@classmethod
|
|
273
|
-
def set_log_levels(cls, v: Any, info: ValidationInfo) -> dict:
|
|
274
|
-
return {
|
|
275
|
-
"api": info.data["API_LOG_LEVEL"],
|
|
276
|
-
"main": info.data["MAIN_LOG_LEVEL"],
|
|
277
|
-
"mqtt": info.data["MQTT_LOG_LEVEL"],
|
|
278
|
-
"state_machine": info.data["STATE_MACHINE_LOG_LEVEL"],
|
|
279
|
-
"uploader": info.data["UPLOADER_LOG_LEVEL"],
|
|
280
|
-
"console": info.data["CONSOLE_LOG_LEVEL"],
|
|
281
|
-
"urllib3": info.data["URLLIB3_LOG_LEVEL"],
|
|
282
|
-
"uvicorn": info.data["UVICORN_LOG_LEVEL"],
|
|
283
|
-
"azure": info.data["AZURE_LOG_LEVEL"],
|
|
284
|
-
}
|
|
285
|
-
|
|
286
248
|
@field_validator(
|
|
287
249
|
"TOPIC_ISAR_STATUS",
|
|
288
250
|
"TOPIC_ISAR_MISSION",
|
|
289
251
|
"TOPIC_ISAR_TASK",
|
|
290
|
-
"TOPIC_ISAR_STEP",
|
|
291
252
|
"TOPIC_ISAR_ROBOT_INFO",
|
|
292
253
|
"TOPIC_ISAR_ROBOT_HEARTBEAT",
|
|
293
254
|
"TOPIC_ISAR_INSPECTION_RESULT",
|
|
255
|
+
"TOPIC_ISAR_INSPECTION_VALUE",
|
|
256
|
+
"TOPIC_ISAR_STARTUP",
|
|
257
|
+
"TOPIC_ISAR_INTERVENTION_NEEDED",
|
|
258
|
+
"TOPIC_ISAR_MISSION_ABORTED",
|
|
294
259
|
)
|
|
295
260
|
@classmethod
|
|
296
261
|
def prefix_isar_topics(cls, v: Any, info: ValidationInfo):
|
|
@@ -304,7 +269,12 @@ class Settings(BaseSettings):
|
|
|
304
269
|
)
|
|
305
270
|
|
|
306
271
|
|
|
307
|
-
|
|
272
|
+
env = os.environ.get("ISAR_ENV")
|
|
273
|
+
|
|
274
|
+
if env == "test":
|
|
275
|
+
load_dotenv(".env.test", override=True)
|
|
276
|
+
else:
|
|
277
|
+
load_dotenv()
|
|
308
278
|
settings = Settings()
|
|
309
279
|
|
|
310
280
|
|
|
@@ -324,17 +294,12 @@ class RobotSettings(BaseSettings):
|
|
|
324
294
|
|
|
325
295
|
# ISAR steps the robot is capable of performing
|
|
326
296
|
# This should be set in the robot package settings.env file
|
|
327
|
-
CAPABILITIES: List[str] = Field(default=["
|
|
297
|
+
CAPABILITIES: List[str] = Field(default=["take_image"])
|
|
328
298
|
|
|
329
299
|
# Model of the robot which ISAR is connected to
|
|
330
300
|
# This should be set in the robot package settings.env file
|
|
331
301
|
ROBOT_MODEL: RobotModel = Field(default=RobotModel.Robot) # type: ignore
|
|
332
302
|
|
|
333
|
-
# Valid arm poses that the robot may utilize
|
|
334
|
-
# This should be set in the robot package settings.env file
|
|
335
|
-
# Note that if the robot does not support moving an arm this will be None and
|
|
336
|
-
# the functionality will be unavailable
|
|
337
|
-
VALID_ARM_POSES: Optional[List[str]] = Field(default=None)
|
|
338
303
|
model_config = SettingsConfigDict(
|
|
339
304
|
env_file_encoding="utf-8", case_sensitive=True, extra="ignore"
|
|
340
305
|
)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from threading import Event as ThreadEvent
|
|
6
|
+
from typing import TYPE_CHECKING, Callable, Generic, List, Optional, TypeVar
|
|
7
|
+
|
|
8
|
+
from transitions import State
|
|
9
|
+
|
|
10
|
+
from isar.config.settings import settings
|
|
11
|
+
from isar.models.events import Event
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class EventHandlerMapping(Generic[T]):
|
|
18
|
+
name: str
|
|
19
|
+
event: Event[T]
|
|
20
|
+
handler: Callable[[Event[T]], Optional[Callable]]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class TimeoutHandlerMapping:
|
|
25
|
+
name: str
|
|
26
|
+
timeout_in_seconds: float
|
|
27
|
+
handler: Callable[[], Optional[Callable]]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from isar.state_machine.state_machine import StateMachine
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EventHandlerBase(State):
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
state_machine: "StateMachine",
|
|
38
|
+
state_name: str,
|
|
39
|
+
event_handler_mappings: List[EventHandlerMapping],
|
|
40
|
+
timers: List[TimeoutHandlerMapping] = [],
|
|
41
|
+
on_entry: Optional[Callable[[], None]] = None,
|
|
42
|
+
on_transition: Optional[Callable[[], None]] = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
|
|
45
|
+
super().__init__(name=state_name, on_enter=self.start)
|
|
46
|
+
self.state_machine: "StateMachine" = state_machine
|
|
47
|
+
self.logger = logging.getLogger("state_machine")
|
|
48
|
+
self.events = state_machine.events
|
|
49
|
+
self.signal_state_machine_to_stop: ThreadEvent = (
|
|
50
|
+
state_machine.signal_state_machine_to_stop
|
|
51
|
+
)
|
|
52
|
+
self.event_handler_mappings = event_handler_mappings
|
|
53
|
+
self.state_name: str = state_name
|
|
54
|
+
self.timers = timers
|
|
55
|
+
self.on_entry = on_entry
|
|
56
|
+
self.on_transition = on_transition
|
|
57
|
+
|
|
58
|
+
def start(self) -> None:
|
|
59
|
+
self.state_machine.update_state()
|
|
60
|
+
if self.on_entry:
|
|
61
|
+
self.on_entry()
|
|
62
|
+
self._run()
|
|
63
|
+
|
|
64
|
+
def stop(self) -> None:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
def get_event_handler_by_name(
|
|
68
|
+
self, event_handler_name: str
|
|
69
|
+
) -> Optional[EventHandlerMapping]:
|
|
70
|
+
filtered_handlers = list(
|
|
71
|
+
filter(
|
|
72
|
+
lambda mapping: mapping.name == event_handler_name,
|
|
73
|
+
self.event_handler_mappings,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
return filtered_handlers[0] if len(filtered_handlers) > 0 else None
|
|
77
|
+
|
|
78
|
+
def get_event_timer_by_name(
|
|
79
|
+
self, event_timer_name: str
|
|
80
|
+
) -> Optional[TimeoutHandlerMapping]:
|
|
81
|
+
filtered_timers = list(
|
|
82
|
+
filter(
|
|
83
|
+
lambda mapping: mapping.name == event_timer_name,
|
|
84
|
+
self.timers,
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
return filtered_timers[0] if len(filtered_timers) > 0 else None
|
|
88
|
+
|
|
89
|
+
def _run(self) -> None:
|
|
90
|
+
should_exit_state: bool = False
|
|
91
|
+
timers = deepcopy(self.timers)
|
|
92
|
+
entered_time = time.time()
|
|
93
|
+
while True:
|
|
94
|
+
if self.signal_state_machine_to_stop.is_set():
|
|
95
|
+
self.logger.info(
|
|
96
|
+
"Stopping state machine from %s state", self.state_name
|
|
97
|
+
)
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
for timer in timers:
|
|
101
|
+
if time.time() - entered_time > timer.timeout_in_seconds:
|
|
102
|
+
transition_func = timer.handler()
|
|
103
|
+
timers.remove(timer)
|
|
104
|
+
if transition_func is not None:
|
|
105
|
+
transition_func()
|
|
106
|
+
should_exit_state = True
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
if should_exit_state:
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
for handler_mapping in self.event_handler_mappings:
|
|
113
|
+
transition_func = handler_mapping.handler(handler_mapping.event)
|
|
114
|
+
if transition_func is not None:
|
|
115
|
+
transition_func()
|
|
116
|
+
should_exit_state = True
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
if should_exit_state:
|
|
120
|
+
break
|
|
121
|
+
time.sleep(settings.FSM_SLEEP_TIME)
|
|
122
|
+
if self.on_transition:
|
|
123
|
+
self.on_transition()
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
|
|
4
|
-
from alitra import Frame
|
|
5
|
-
from injector import inject
|
|
6
|
-
|
|
7
5
|
from isar.config.settings import settings
|
|
8
6
|
from isar.mission_planner.mission_planner_interface import (
|
|
9
7
|
MissionNotFoundError,
|
|
10
8
|
MissionPlannerError,
|
|
11
9
|
MissionPlannerInterface,
|
|
12
10
|
)
|
|
13
|
-
from isar.services.readers.base_reader import BaseReader, BaseReaderError
|
|
14
11
|
from robot_interface.models.mission.mission import Mission
|
|
15
12
|
|
|
16
13
|
logger = logging.getLogger("api")
|
|
17
14
|
|
|
18
15
|
|
|
19
16
|
class LocalPlanner(MissionPlannerInterface):
|
|
20
|
-
@inject
|
|
21
17
|
def __init__(self):
|
|
22
18
|
self.predefined_mission_folder = Path(settings.PREDEFINED_MISSIONS_FOLDER)
|
|
23
19
|
|
|
@@ -37,15 +33,10 @@ class LocalPlanner(MissionPlannerInterface):
|
|
|
37
33
|
|
|
38
34
|
@staticmethod
|
|
39
35
|
def read_mission_from_file(mission_path: Path) -> Mission:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
dataclass_dict=mission_dict,
|
|
43
|
-
target_dataclass=Mission,
|
|
44
|
-
cast_config=[Frame],
|
|
45
|
-
strict_config=True,
|
|
46
|
-
)
|
|
36
|
+
with open(mission_path) as json_file:
|
|
37
|
+
mission_dict = json.load(json_file)
|
|
47
38
|
|
|
48
|
-
return
|
|
39
|
+
return Mission(**mission_dict)
|
|
49
40
|
|
|
50
41
|
def get_predefined_missions(self) -> dict:
|
|
51
42
|
missions: dict = {}
|
|
@@ -54,13 +45,8 @@ class LocalPlanner(MissionPlannerInterface):
|
|
|
54
45
|
for file in json_files:
|
|
55
46
|
mission_name = file.stem
|
|
56
47
|
path_to_file = self.predefined_mission_folder.joinpath(file.name)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
except BaseReaderError as e:
|
|
60
|
-
logger.warning(
|
|
61
|
-
f"Failed to read predefined mission {path_to_file} \n {e}"
|
|
62
|
-
)
|
|
63
|
-
continue
|
|
48
|
+
|
|
49
|
+
mission: Mission = self.read_mission_from_file(path_to_file)
|
|
64
50
|
if mission.id in invalid_mission_ids:
|
|
65
51
|
logger.warning(
|
|
66
52
|
f"Duplicate mission id {mission.id} : {path_to_file.as_posix()}"
|