isar 1.32.2__py3-none-any.whl → 1.33.0__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.
Potentially problematic release.
This version of isar might be problematic. Click here for more details.
- isar/apis/models/models.py +6 -0
- isar/config/open_telemetry.py +52 -12
- isar/config/settings.py +20 -3
- isar/eventhandlers/eventhandler.py +22 -0
- isar/models/events.py +14 -4
- isar/robot/robot_status.py +3 -0
- isar/services/utilities/scheduling_utilities.py +49 -21
- isar/state_machine/state_machine.py +38 -0
- isar/state_machine/states/await_next_mission.py +3 -1
- isar/state_machine/states/home.py +3 -1
- isar/state_machine/states/monitor.py +24 -0
- isar/state_machine/states/recharging.py +44 -0
- isar/state_machine/states/returning_home.py +22 -2
- isar/state_machine/states/robot_standing_still.py +3 -1
- isar/state_machine/states_enum.py +1 -0
- isar/state_machine/transitions/functions/return_home.py +17 -1
- isar/state_machine/transitions/functions/start_mission.py +10 -2
- isar/state_machine/transitions/return_home.py +33 -1
- isar/state_machine/transitions/robot_status.py +10 -0
- isar/state_machine/utils/common_event_handlers.py +18 -3
- {isar-1.32.2.dist-info → isar-1.33.0.dist-info}/METADATA +1 -1
- {isar-1.32.2.dist-info → isar-1.33.0.dist-info}/RECORD +30 -29
- robot_interface/models/inspection/inspection.py +6 -15
- robot_interface/models/mission/status.py +1 -0
- robot_interface/robot_interface.py +27 -0
- robot_interface/telemetry/payloads.py +10 -0
- {isar-1.32.2.dist-info → isar-1.33.0.dist-info}/WHEEL +0 -0
- {isar-1.32.2.dist-info → isar-1.33.0.dist-info}/entry_points.txt +0 -0
- {isar-1.32.2.dist-info → isar-1.33.0.dist-info}/licenses/LICENSE +0 -0
- {isar-1.32.2.dist-info → isar-1.33.0.dist-info}/top_level.txt +0 -0
isar/apis/models/models.py
CHANGED
|
@@ -26,6 +26,12 @@ class ControlMissionResponse(BaseModel):
|
|
|
26
26
|
task_status: Optional[str]
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
class MissionStartResponse(BaseModel):
|
|
30
|
+
mission_id: Optional[str] = None
|
|
31
|
+
mission_started: bool
|
|
32
|
+
mission_not_started_reason: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
|
|
29
35
|
class RobotInfoResponse(BaseModel):
|
|
30
36
|
robot_package: str
|
|
31
37
|
isar_id: str
|
isar/config/open_telemetry.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from urllib.parse import urljoin
|
|
2
3
|
|
|
3
4
|
from azure.monitor.opentelemetry.exporter import (
|
|
4
5
|
AzureMonitorLogExporter,
|
|
@@ -7,6 +8,12 @@ from azure.monitor.opentelemetry.exporter import (
|
|
|
7
8
|
from fastapi import FastAPI
|
|
8
9
|
from opentelemetry import trace
|
|
9
10
|
from opentelemetry._logs import set_logger_provider
|
|
11
|
+
from opentelemetry.exporter.otlp.proto.http._log_exporter import (
|
|
12
|
+
OTLPLogExporter as OTLPHttpLogExporter,
|
|
13
|
+
)
|
|
14
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
|
15
|
+
OTLPSpanExporter as OTLPHttpSpanExporter,
|
|
16
|
+
)
|
|
10
17
|
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
11
18
|
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
|
12
19
|
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
|
@@ -17,22 +24,45 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
|
17
24
|
from isar.config.log import load_log_config
|
|
18
25
|
from isar.config.settings import settings
|
|
19
26
|
|
|
27
|
+
logging.getLogger("opentelemetry.sdk").setLevel(logging.CRITICAL)
|
|
28
|
+
|
|
20
29
|
|
|
21
30
|
def setup_open_telemetry(app: FastAPI) -> None:
|
|
22
|
-
if not settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED:
|
|
23
|
-
return
|
|
24
|
-
trace_exporter, log_exporter = get_azure_monitor_exporters()
|
|
25
31
|
|
|
26
|
-
service_name = settings.
|
|
32
|
+
service_name = settings.ROBOT_NAME
|
|
27
33
|
resource = Resource.create({SERVICE_NAME: service_name})
|
|
28
34
|
|
|
29
35
|
tracer_provider = TracerProvider(resource=resource)
|
|
30
|
-
tracer_provider.add_span_processor(BatchSpanProcessor(trace_exporter))
|
|
31
|
-
trace.set_tracer_provider(tracer_provider)
|
|
32
|
-
|
|
33
36
|
log_provider = LoggerProvider(resource=resource)
|
|
37
|
+
|
|
38
|
+
if settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED:
|
|
39
|
+
print("[OTEL] Azure Monitor exporters enabled")
|
|
40
|
+
azure_monitor_trace_exporter, azure_monitor_log_exporter = (
|
|
41
|
+
get_azure_monitor_exporters()
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
tracer_provider.add_span_processor(
|
|
45
|
+
BatchSpanProcessor(azure_monitor_trace_exporter)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
log_provider.add_log_record_processor(
|
|
49
|
+
BatchLogRecordProcessor(azure_monitor_log_exporter)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
otlp_exporter_endpoint = settings.OPEN_TELEMETRY_OTLP_EXPORTER_ENDPOINT
|
|
53
|
+
if otlp_exporter_endpoint:
|
|
54
|
+
print(f"[OTEL] OTLP exporters enabled, endpoint={otlp_exporter_endpoint}")
|
|
55
|
+
otlp_trace_exporter, otlp_log_exporter = get_otlp_exporters(
|
|
56
|
+
otlp_exporter_endpoint
|
|
57
|
+
)
|
|
58
|
+
tracer_provider.add_span_processor(BatchSpanProcessor(otlp_trace_exporter))
|
|
59
|
+
|
|
60
|
+
log_provider.add_log_record_processor(
|
|
61
|
+
BatchLogRecordProcessor(otlp_log_exporter)
|
|
62
|
+
)
|
|
63
|
+
|
|
34
64
|
set_logger_provider(log_provider)
|
|
35
|
-
|
|
65
|
+
trace.set_tracer_provider(tracer_provider)
|
|
36
66
|
|
|
37
67
|
handler = LoggingHandler(logger_provider=log_provider)
|
|
38
68
|
attach_loggers_for_open_telemetry(handler)
|
|
@@ -51,12 +81,22 @@ def attach_loggers_for_open_telemetry(handler: LoggingHandler):
|
|
|
51
81
|
def get_azure_monitor_exporters() -> (
|
|
52
82
|
tuple[AzureMonitorTraceExporter, AzureMonitorLogExporter]
|
|
53
83
|
):
|
|
54
|
-
"""
|
|
55
|
-
If connection string is defined in environment variables, then use it to create Azure Monitor Exporters.
|
|
56
|
-
Else use Azure Managed Identity to create Azure Monitor Exporters.
|
|
57
|
-
"""
|
|
58
84
|
connection_string = settings.APPLICATIONINSIGHTS_CONNECTION_STRING
|
|
59
85
|
trace_exporter = AzureMonitorTraceExporter(connection_string=connection_string)
|
|
60
86
|
log_exporter = AzureMonitorLogExporter(connection_string=connection_string)
|
|
61
87
|
|
|
62
88
|
return trace_exporter, log_exporter
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_otlp_exporters(
|
|
92
|
+
endpoint: str,
|
|
93
|
+
) -> tuple[OTLPHttpSpanExporter, OTLPHttpLogExporter]:
|
|
94
|
+
base = endpoint.rstrip("/") + "/"
|
|
95
|
+
trace_ep = urljoin(base, "v1/traces")
|
|
96
|
+
log_ep = urljoin(base, "v1/logs")
|
|
97
|
+
|
|
98
|
+
print("[OTEL] Using HTTP/Protobuf protocol for OpenTelemetry export")
|
|
99
|
+
print(f"[OTEL] traces → {trace_ep}")
|
|
100
|
+
print(f"[OTEL] logs → {log_ep}")
|
|
101
|
+
|
|
102
|
+
return OTLPHttpSpanExporter(endpoint=trace_ep), OTLPHttpLogExporter(endpoint=log_ep)
|
isar/config/settings.py
CHANGED
|
@@ -12,8 +12,9 @@ from robot_interface.telemetry.payloads import DocumentInfo
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class Settings(BaseSettings):
|
|
15
|
-
#
|
|
16
|
-
|
|
15
|
+
# Endpoint open telemetry will export telemetry in the otlp protocol to
|
|
16
|
+
OPEN_TELEMETRY_OTLP_EXPORTER_ENDPOINT: str = Field(default="http://localhost:4318")
|
|
17
|
+
|
|
17
18
|
# Connection string for Azure Application Insights
|
|
18
19
|
# This is optional and it will use managed identity if not set
|
|
19
20
|
APPLICATIONINSIGHTS_CONNECTION_STRING: str = Field(default="")
|
|
@@ -29,7 +30,7 @@ class Settings(BaseSettings):
|
|
|
29
30
|
REQUEST_TIMEOUT: int = Field(default=30)
|
|
30
31
|
|
|
31
32
|
# Timeout in seconds for checking whether there is a message on a queue
|
|
32
|
-
QUEUE_TIMEOUT: int = Field(default=
|
|
33
|
+
QUEUE_TIMEOUT: int = Field(default=3)
|
|
33
34
|
|
|
34
35
|
# Sleep time for while loops in the finite state machine in seconds
|
|
35
36
|
# The sleep is used to throttle the system on every iteration in the loop
|
|
@@ -80,6 +81,15 @@ class Settings(BaseSettings):
|
|
|
80
81
|
ROBOT_API_STATUS_POLL_INTERVAL: float = Field(default=5)
|
|
81
82
|
THREAD_CHECK_INTERVAL: float = Field(default=0.01)
|
|
82
83
|
|
|
84
|
+
# Determines the minimum battery level the robot must have to start a mission
|
|
85
|
+
# If it drops below this level it will recharge to the value set by
|
|
86
|
+
# ROBOT_BATTERY_RECHARGE_THRESHOLD before starting new missions
|
|
87
|
+
ROBOT_MISSION_BATTERY_START_THRESHOLD: float = Field(default=25.0)
|
|
88
|
+
|
|
89
|
+
# Determines the minimum battery threshold to consider the robot recharged
|
|
90
|
+
# and ready for more missions, after having run low on charge
|
|
91
|
+
ROBOT_BATTERY_RECHARGE_THRESHOLD: float = Field(default=80.0)
|
|
92
|
+
|
|
83
93
|
# FastAPI host
|
|
84
94
|
API_HOST_VIEWED_EXTERNALLY: str = Field(default="0.0.0.0")
|
|
85
95
|
|
|
@@ -89,6 +99,9 @@ class Settings(BaseSettings):
|
|
|
89
99
|
# Determines how long delay time should be allowed before returning home
|
|
90
100
|
RETURN_HOME_DELAY: int = Field(default=10)
|
|
91
101
|
|
|
102
|
+
# Sets how many times the robot should try to return home if a return home fails
|
|
103
|
+
RETURN_HOME_RETRY_LIMIT: int = Field(default=5)
|
|
104
|
+
|
|
92
105
|
# Determines which mission planner module is used by ISAR
|
|
93
106
|
MISSION_PLANNER: str = Field(default="local")
|
|
94
107
|
|
|
@@ -212,6 +225,9 @@ class Settings(BaseSettings):
|
|
|
212
225
|
# List of MQTT Topics
|
|
213
226
|
TOPIC_ISAR_STATUS: str = Field(default="status", validate_default=True)
|
|
214
227
|
TOPIC_ISAR_MISSION: str = Field(default="mission", validate_default=True)
|
|
228
|
+
TOPIC_ISAR_MISSION_ABORTED: str = Field(
|
|
229
|
+
default="aborted_mission", validate_default=True
|
|
230
|
+
)
|
|
215
231
|
TOPIC_ISAR_TASK: str = Field(default="task", validate_default=True)
|
|
216
232
|
TOPIC_ISAR_INSPECTION_RESULT: str = Field(
|
|
217
233
|
default="inspection_result", validate_default=True
|
|
@@ -279,6 +295,7 @@ class Settings(BaseSettings):
|
|
|
279
295
|
"TOPIC_ISAR_INSPECTION_VALUE",
|
|
280
296
|
"TOPIC_ISAR_STARTUP",
|
|
281
297
|
"TOPIC_ISAR_INTERVENTION_NEEDED",
|
|
298
|
+
"TOPIC_ISAR_MISSION_ABORTED",
|
|
282
299
|
)
|
|
283
300
|
@classmethod
|
|
284
301
|
def prefix_isar_topics(cls, v: Any, info: ValidationInfo):
|
|
@@ -58,6 +58,28 @@ class EventHandlerBase(State):
|
|
|
58
58
|
def stop(self) -> None:
|
|
59
59
|
return
|
|
60
60
|
|
|
61
|
+
def get_event_handler_by_name(
|
|
62
|
+
self, event_handler_name: str
|
|
63
|
+
) -> Optional[EventHandlerMapping]:
|
|
64
|
+
filtered_handlers = list(
|
|
65
|
+
filter(
|
|
66
|
+
lambda mapping: mapping.name == event_handler_name,
|
|
67
|
+
self.event_handler_mappings,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
return filtered_handlers[0] if len(filtered_handlers) > 0 else None
|
|
71
|
+
|
|
72
|
+
def get_event_timer_by_name(
|
|
73
|
+
self, event_timer_name: str
|
|
74
|
+
) -> Optional[TimeoutHandlerMapping]:
|
|
75
|
+
filtered_timers = list(
|
|
76
|
+
filter(
|
|
77
|
+
lambda mapping: mapping.name == event_timer_name,
|
|
78
|
+
self.timers,
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
return filtered_timers[0] if len(filtered_timers) > 0 else None
|
|
82
|
+
|
|
61
83
|
def _run(self) -> None:
|
|
62
84
|
should_exit_state: bool = False
|
|
63
85
|
timers = deepcopy(self.timers)
|
isar/models/events.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Generic, Optional, TypeVar
|
|
|
4
4
|
|
|
5
5
|
from transitions import State
|
|
6
6
|
|
|
7
|
-
from isar.apis.models.models import ControlMissionResponse
|
|
7
|
+
from isar.apis.models.models import ControlMissionResponse, MissionStartResponse
|
|
8
8
|
from isar.config.settings import settings
|
|
9
9
|
from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
|
|
10
10
|
from robot_interface.models.mission.mission import Mission
|
|
@@ -20,8 +20,13 @@ class Event(Queue[T]):
|
|
|
20
20
|
def __init__(self) -> None:
|
|
21
21
|
super().__init__(maxsize=1)
|
|
22
22
|
|
|
23
|
-
def trigger_event(self, data: T) -> None:
|
|
24
|
-
|
|
23
|
+
def trigger_event(self, data: T, timeout: int = None) -> None:
|
|
24
|
+
try:
|
|
25
|
+
self.put(data, timeout=timeout)
|
|
26
|
+
except Exception:
|
|
27
|
+
if timeout is not None:
|
|
28
|
+
raise EventTimeoutError
|
|
29
|
+
return None
|
|
25
30
|
|
|
26
31
|
def consume_event(self, timeout: int = None) -> Optional[T]:
|
|
27
32
|
try:
|
|
@@ -81,7 +86,7 @@ class APIEvent(Generic[T1, T2]):
|
|
|
81
86
|
|
|
82
87
|
class APIRequests:
|
|
83
88
|
def __init__(self) -> None:
|
|
84
|
-
self.start_mission: APIEvent[Mission,
|
|
89
|
+
self.start_mission: APIEvent[Mission, MissionStartResponse] = APIEvent()
|
|
85
90
|
self.stop_mission: APIEvent[str, ControlMissionResponse] = APIEvent()
|
|
86
91
|
self.pause_mission: APIEvent[bool, ControlMissionResponse] = APIEvent()
|
|
87
92
|
self.resume_mission: APIEvent[bool, ControlMissionResponse] = APIEvent()
|
|
@@ -113,7 +118,12 @@ class SharedState:
|
|
|
113
118
|
self.state: Event[State] = Event()
|
|
114
119
|
self.robot_status: Event[RobotStatus] = Event()
|
|
115
120
|
self.state_machine_current_task: Event[TASKS] = Event()
|
|
121
|
+
self.robot_battery_level: Event[float] = Event()
|
|
116
122
|
|
|
117
123
|
|
|
118
124
|
class EventTimeoutError(Exception):
|
|
119
125
|
pass
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class EventConflictError(Exception):
|
|
129
|
+
pass
|
isar/robot/robot_status.py
CHANGED
|
@@ -48,7 +48,10 @@ class RobotStatusThread(Thread):
|
|
|
48
48
|
self.last_robot_status_poll_time = time.time()
|
|
49
49
|
|
|
50
50
|
robot_status = self.robot.robot_status()
|
|
51
|
+
robot_battery_level = self.robot.get_battery_level()
|
|
52
|
+
|
|
51
53
|
self.shared_state.robot_status.update(robot_status)
|
|
54
|
+
self.shared_state.robot_battery_level.update(robot_battery_level)
|
|
52
55
|
except RobotException as e:
|
|
53
56
|
self.logger.error(f"Failed to retrieve robot status: {e}")
|
|
54
57
|
continue
|
|
@@ -16,6 +16,7 @@ from isar.mission_planner.mission_planner_interface import (
|
|
|
16
16
|
from isar.models.events import (
|
|
17
17
|
APIEvent,
|
|
18
18
|
APIRequests,
|
|
19
|
+
EventConflictError,
|
|
19
20
|
Events,
|
|
20
21
|
EventTimeoutError,
|
|
21
22
|
SharedState,
|
|
@@ -176,16 +177,28 @@ class SchedulingUtilities:
|
|
|
176
177
|
If there is a timeout while communicating with the state machine
|
|
177
178
|
"""
|
|
178
179
|
try:
|
|
179
|
-
self._send_command(
|
|
180
|
+
mission_start_response = self._send_command(
|
|
180
181
|
deepcopy(mission),
|
|
181
182
|
self.api_events.start_mission,
|
|
182
183
|
)
|
|
184
|
+
if not mission_start_response.mission_started:
|
|
185
|
+
self.logger.warning(
|
|
186
|
+
f"Mission failed to start - {mission_start_response.mission_not_started_reason}"
|
|
187
|
+
)
|
|
188
|
+
raise HTTPException(
|
|
189
|
+
status_code=HTTPStatus.CONFLICT,
|
|
190
|
+
detail=mission_start_response.mission_not_started_reason,
|
|
191
|
+
)
|
|
192
|
+
except EventConflictError:
|
|
193
|
+
error_message = "Previous mission request is still being processed"
|
|
194
|
+
self.logger.warning(error_message)
|
|
195
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
183
196
|
except EventTimeoutError:
|
|
184
|
-
error_message =
|
|
185
|
-
|
|
186
|
-
raise HTTPException(
|
|
187
|
-
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
197
|
+
error_message = (
|
|
198
|
+
"State machine has entered a state which cannot start a mission"
|
|
188
199
|
)
|
|
200
|
+
self.logger.warning(error_message)
|
|
201
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
189
202
|
self.logger.info("OK - Mission started in ISAR")
|
|
190
203
|
|
|
191
204
|
def return_home(
|
|
@@ -203,14 +216,14 @@ class SchedulingUtilities:
|
|
|
203
216
|
True,
|
|
204
217
|
self.api_events.return_home,
|
|
205
218
|
)
|
|
219
|
+
except EventConflictError:
|
|
220
|
+
error_message = "Previous return home request is still being processed"
|
|
221
|
+
self.logger.warning(error_message)
|
|
222
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
206
223
|
except EventTimeoutError:
|
|
207
|
-
error_message =
|
|
208
|
-
|
|
209
|
-
)
|
|
210
|
-
self.logger.error(error_message)
|
|
211
|
-
raise HTTPException(
|
|
212
|
-
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
213
|
-
)
|
|
224
|
+
error_message = "State machine has entered a state which cannot start a return home mission"
|
|
225
|
+
self.logger.warning(error_message)
|
|
226
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
214
227
|
self.logger.info("OK - Return home mission started in ISAR")
|
|
215
228
|
|
|
216
229
|
def pause_mission(self) -> ControlMissionResponse:
|
|
@@ -225,12 +238,16 @@ class SchedulingUtilities:
|
|
|
225
238
|
response = self._send_command(True, self.api_events.pause_mission)
|
|
226
239
|
self.logger.info("OK - Mission successfully paused")
|
|
227
240
|
return response
|
|
241
|
+
except EventConflictError:
|
|
242
|
+
error_message = "Previous pause mission request is still being processed"
|
|
243
|
+
self.logger.warning(error_message)
|
|
244
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
228
245
|
except EventTimeoutError:
|
|
229
|
-
error_message =
|
|
230
|
-
|
|
231
|
-
raise HTTPException(
|
|
232
|
-
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
246
|
+
error_message = (
|
|
247
|
+
"State machine has entered a state which cannot pause a mission"
|
|
233
248
|
)
|
|
249
|
+
self.logger.warning(error_message)
|
|
250
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
234
251
|
|
|
235
252
|
def resume_mission(self) -> ControlMissionResponse:
|
|
236
253
|
"""Resume mission
|
|
@@ -244,6 +261,10 @@ class SchedulingUtilities:
|
|
|
244
261
|
response = self._send_command(True, self.api_events.resume_mission)
|
|
245
262
|
self.logger.info("OK - Mission successfully resumed")
|
|
246
263
|
return response
|
|
264
|
+
except EventConflictError:
|
|
265
|
+
error_message = "Previous resume mission request is still being processed"
|
|
266
|
+
self.logger.warning(error_message)
|
|
267
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
247
268
|
except EventTimeoutError:
|
|
248
269
|
error_message = "Internal Server Error - Failed to resume mission"
|
|
249
270
|
self.logger.error(error_message)
|
|
@@ -281,12 +302,16 @@ class SchedulingUtilities:
|
|
|
281
302
|
raise HTTPException(
|
|
282
303
|
status_code=HTTPStatus.CONFLICT, detail=error_message
|
|
283
304
|
)
|
|
305
|
+
except EventConflictError:
|
|
306
|
+
error_message = "Previous stop mission request is still being processed"
|
|
307
|
+
self.logger.warning(error_message)
|
|
308
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
284
309
|
except EventTimeoutError:
|
|
285
|
-
error_message =
|
|
286
|
-
|
|
287
|
-
raise HTTPException(
|
|
288
|
-
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
310
|
+
error_message = (
|
|
311
|
+
"State machine has entered a state which cannot stop a mission"
|
|
289
312
|
)
|
|
313
|
+
self.logger.warning(error_message)
|
|
314
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
290
315
|
self.logger.info("OK - Mission successfully stopped")
|
|
291
316
|
return stop_mission_response
|
|
292
317
|
|
|
@@ -311,8 +336,11 @@ class SchedulingUtilities:
|
|
|
311
336
|
)
|
|
312
337
|
|
|
313
338
|
def _send_command(self, input: T1, api_event: APIEvent[T1, T2]) -> T2:
|
|
314
|
-
api_event.request.
|
|
339
|
+
if api_event.request.has_event() or api_event.response.has_event():
|
|
340
|
+
raise EventConflictError("API event has already been sent")
|
|
341
|
+
|
|
315
342
|
try:
|
|
343
|
+
api_event.request.trigger_event(input, timeout=1)
|
|
316
344
|
return api_event.response.consume_event(timeout=self.queue_timeout)
|
|
317
345
|
except EventTimeoutError as e:
|
|
318
346
|
self.logger.error("Queue timed out")
|
|
@@ -22,6 +22,7 @@ from isar.state_machine.states.intervention_needed import InterventionNeeded
|
|
|
22
22
|
from isar.state_machine.states.monitor import Monitor
|
|
23
23
|
from isar.state_machine.states.offline import Offline
|
|
24
24
|
from isar.state_machine.states.paused import Paused
|
|
25
|
+
from isar.state_machine.states.recharging import Recharging
|
|
25
26
|
from isar.state_machine.states.returning_home import ReturningHome
|
|
26
27
|
from isar.state_machine.states.robot_standing_still import RobotStandingStill
|
|
27
28
|
from isar.state_machine.states.stopping import Stopping
|
|
@@ -43,6 +44,7 @@ from robot_interface.robot_interface import RobotInterface
|
|
|
43
44
|
from robot_interface.telemetry.mqtt_client import MqttClientInterface
|
|
44
45
|
from robot_interface.telemetry.payloads import (
|
|
45
46
|
InterventionNeededPayload,
|
|
47
|
+
MissionAbortedPayload,
|
|
46
48
|
MissionPayload,
|
|
47
49
|
RobotStatusPayload,
|
|
48
50
|
TaskPayload,
|
|
@@ -108,6 +110,7 @@ class StateMachine(object):
|
|
|
108
110
|
# Status states
|
|
109
111
|
self.offline_state: State = Offline(self)
|
|
110
112
|
self.blocked_protective_stopping_state: State = BlockedProtectiveStop(self)
|
|
113
|
+
self.recharging_state: State = Recharging(self)
|
|
111
114
|
|
|
112
115
|
# Error and special status states
|
|
113
116
|
self.unknown_status_state: State = UnknownStatus(self)
|
|
@@ -124,6 +127,7 @@ class StateMachine(object):
|
|
|
124
127
|
self.blocked_protective_stopping_state,
|
|
125
128
|
self.unknown_status_state,
|
|
126
129
|
self.intervention_needed_state,
|
|
130
|
+
self.recharging_state,
|
|
127
131
|
]
|
|
128
132
|
|
|
129
133
|
self.machine = Machine(
|
|
@@ -189,6 +193,12 @@ class StateMachine(object):
|
|
|
189
193
|
self.current_task = None
|
|
190
194
|
self.send_task_status()
|
|
191
195
|
|
|
196
|
+
def battery_level_is_above_mission_start_threshold(self):
|
|
197
|
+
return (
|
|
198
|
+
not self.shared_state.robot_battery_level.check()
|
|
199
|
+
< settings.ROBOT_MISSION_BATTERY_START_THRESHOLD
|
|
200
|
+
)
|
|
201
|
+
|
|
192
202
|
def update_state(self):
|
|
193
203
|
"""Updates the current state of the state machine."""
|
|
194
204
|
self.current_state = States(self.state) # type: ignore
|
|
@@ -227,6 +237,32 @@ class StateMachine(object):
|
|
|
227
237
|
f"Task: {str(task.id)[:8]} was reported as task.status by the robot"
|
|
228
238
|
)
|
|
229
239
|
|
|
240
|
+
def publish_mission_aborted(self, reason: str, can_be_continued: bool) -> None:
|
|
241
|
+
if not self.mqtt_publisher:
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
if self.current_mission is None:
|
|
245
|
+
self.logger.warning(
|
|
246
|
+
"Could not publish mission aborted message. No ongoing mission."
|
|
247
|
+
)
|
|
248
|
+
return
|
|
249
|
+
|
|
250
|
+
payload: MissionAbortedPayload = MissionAbortedPayload(
|
|
251
|
+
isar_id=settings.ISAR_ID,
|
|
252
|
+
robot_name=settings.ROBOT_NAME,
|
|
253
|
+
mission_id=self.current_mission.id,
|
|
254
|
+
reason=reason,
|
|
255
|
+
can_be_continued=can_be_continued,
|
|
256
|
+
timestamp=datetime.now(timezone.utc),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
self.mqtt_publisher.publish(
|
|
260
|
+
topic=settings.TOPIC_ISAR_MISSION_ABORTED,
|
|
261
|
+
payload=json.dumps(payload, cls=EnhancedJSONEncoder),
|
|
262
|
+
qos=1,
|
|
263
|
+
retain=True,
|
|
264
|
+
)
|
|
265
|
+
|
|
230
266
|
def publish_mission_status(self) -> None:
|
|
231
267
|
if not self.mqtt_publisher:
|
|
232
268
|
return
|
|
@@ -339,6 +375,8 @@ class StateMachine(object):
|
|
|
339
375
|
return RobotStatus.BlockedProtectiveStop
|
|
340
376
|
elif self.current_state == States.InterventionNeeded:
|
|
341
377
|
return RobotStatus.InterventionNeeded
|
|
378
|
+
elif self.current_state == States.Recharging:
|
|
379
|
+
return RobotStatus.Recharging
|
|
342
380
|
else:
|
|
343
381
|
return RobotStatus.Busy
|
|
344
382
|
|
|
@@ -25,7 +25,9 @@ class AwaitNextMission(EventHandlerBase):
|
|
|
25
25
|
EventHandlerMapping(
|
|
26
26
|
name="start_mission_event",
|
|
27
27
|
event=events.api_requests.start_mission.request,
|
|
28
|
-
handler=lambda event: start_mission_event_handler(
|
|
28
|
+
handler=lambda event: start_mission_event_handler(
|
|
29
|
+
state_machine, event, events.api_requests.start_mission.response
|
|
30
|
+
),
|
|
29
31
|
),
|
|
30
32
|
EventHandlerMapping(
|
|
31
33
|
name="return_home_event",
|
|
@@ -23,7 +23,9 @@ class Home(EventHandlerBase):
|
|
|
23
23
|
EventHandlerMapping(
|
|
24
24
|
name="start_mission_event",
|
|
25
25
|
event=events.api_requests.start_mission.request,
|
|
26
|
-
handler=lambda event: start_mission_event_handler(
|
|
26
|
+
handler=lambda event: start_mission_event_handler(
|
|
27
|
+
state_machine, event, events.api_requests.start_mission.response
|
|
28
|
+
),
|
|
27
29
|
),
|
|
28
30
|
EventHandlerMapping(
|
|
29
31
|
name="return_home_event",
|
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from typing import TYPE_CHECKING, Callable, List, Optional
|
|
4
4
|
|
|
5
|
+
from isar.config.settings import settings
|
|
5
6
|
from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
|
|
6
7
|
from isar.models.events import Event
|
|
7
8
|
from isar.services.utilities.threaded_request import ThreadedRequest
|
|
@@ -23,6 +24,7 @@ class Monitor(EventHandlerBase):
|
|
|
23
24
|
def __init__(self, state_machine: "StateMachine"):
|
|
24
25
|
logger = logging.getLogger("state_machine")
|
|
25
26
|
events = state_machine.events
|
|
27
|
+
shared_state = state_machine.shared_state
|
|
26
28
|
|
|
27
29
|
def _pause_mission_event_handler(event: Event[bool]) -> Optional[Callable]:
|
|
28
30
|
if event.consume_event():
|
|
@@ -46,6 +48,23 @@ class Monitor(EventHandlerBase):
|
|
|
46
48
|
return state_machine.mission_finished # type: ignore
|
|
47
49
|
return None
|
|
48
50
|
|
|
51
|
+
def _robot_battery_level_updated_handler(
|
|
52
|
+
event: Event[float],
|
|
53
|
+
) -> Optional[Callable]:
|
|
54
|
+
battery_level: float = event.check()
|
|
55
|
+
if battery_level < settings.ROBOT_MISSION_BATTERY_START_THRESHOLD:
|
|
56
|
+
state_machine.publish_mission_aborted(
|
|
57
|
+
"Robot battery too low to continue mission", True
|
|
58
|
+
)
|
|
59
|
+
state_machine._finalize()
|
|
60
|
+
state_machine.logger.warning(
|
|
61
|
+
"Cancelling current mission due to low battery"
|
|
62
|
+
)
|
|
63
|
+
state_machine.current_mission = None
|
|
64
|
+
state_machine.current_task = None
|
|
65
|
+
return state_machine.request_return_home # type: ignore
|
|
66
|
+
return None
|
|
67
|
+
|
|
49
68
|
event_handlers: List[EventHandlerMapping] = [
|
|
50
69
|
EventHandlerMapping(
|
|
51
70
|
name="stop_mission_event",
|
|
@@ -85,6 +104,11 @@ class Monitor(EventHandlerBase):
|
|
|
85
104
|
state_machine, _handle_task_completed, event
|
|
86
105
|
),
|
|
87
106
|
),
|
|
107
|
+
EventHandlerMapping(
|
|
108
|
+
name="robot_battery_update_event",
|
|
109
|
+
event=shared_state.robot_battery_level,
|
|
110
|
+
handler=_robot_battery_level_updated_handler,
|
|
111
|
+
),
|
|
88
112
|
]
|
|
89
113
|
super().__init__(
|
|
90
114
|
state_name="monitor",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, List
|
|
2
|
+
|
|
3
|
+
from isar.config.settings import settings
|
|
4
|
+
from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
|
|
5
|
+
from isar.models.events import Event
|
|
6
|
+
from robot_interface.models.mission.status import RobotStatus
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from isar.state_machine.state_machine import StateMachine
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Recharging(EventHandlerBase):
|
|
13
|
+
|
|
14
|
+
def __init__(self, state_machine: "StateMachine"):
|
|
15
|
+
shared_state = state_machine.shared_state
|
|
16
|
+
|
|
17
|
+
def robot_battery_level_updated_handler(event: Event[float]):
|
|
18
|
+
battery_level: float = event.check()
|
|
19
|
+
if battery_level >= settings.ROBOT_BATTERY_RECHARGE_THRESHOLD:
|
|
20
|
+
return state_machine.robot_recharged # type: ignore
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
def robot_offline_handler(event: Event[RobotStatus]):
|
|
24
|
+
robot_status: RobotStatus = event.check()
|
|
25
|
+
if robot_status == RobotStatus.Offline:
|
|
26
|
+
return state_machine.robot_went_offline # type: ignore
|
|
27
|
+
|
|
28
|
+
event_handlers: List[EventHandlerMapping] = [
|
|
29
|
+
EventHandlerMapping(
|
|
30
|
+
name="robot_battery_update_event",
|
|
31
|
+
event=shared_state.robot_battery_level,
|
|
32
|
+
handler=robot_battery_level_updated_handler,
|
|
33
|
+
),
|
|
34
|
+
EventHandlerMapping(
|
|
35
|
+
name="robot_offline_event",
|
|
36
|
+
event=shared_state.robot_status,
|
|
37
|
+
handler=robot_offline_handler,
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
super().__init__(
|
|
41
|
+
state_name="recharging",
|
|
42
|
+
state_machine=state_machine,
|
|
43
|
+
event_handler_mappings=event_handlers,
|
|
44
|
+
)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, Callable, List, Optional
|
|
2
2
|
|
|
3
|
+
from isar.apis.models.models import MissionStartResponse
|
|
3
4
|
from isar.eventhandlers.eventhandler import EventHandlerBase, EventHandlerMapping
|
|
4
|
-
from isar.models.events import Event
|
|
5
|
+
from isar.models.events import Event, EventTimeoutError
|
|
5
6
|
from isar.state_machine.utils.common_event_handlers import (
|
|
6
7
|
mission_failed_event_handler,
|
|
7
8
|
mission_started_event_handler,
|
|
@@ -20,6 +21,7 @@ if TYPE_CHECKING:
|
|
|
20
21
|
class ReturningHome(EventHandlerBase):
|
|
21
22
|
|
|
22
23
|
def __init__(self, state_machine: "StateMachine"):
|
|
24
|
+
self.failed_return_home_attemps: int = 0
|
|
23
25
|
events = state_machine.events
|
|
24
26
|
|
|
25
27
|
def _handle_task_completed(status: TaskStatus):
|
|
@@ -28,13 +30,31 @@ class ReturningHome(EventHandlerBase):
|
|
|
28
30
|
error_reason=ErrorReason.RobotActionException,
|
|
29
31
|
error_description="Return home failed.",
|
|
30
32
|
)
|
|
33
|
+
self.failed_return_home_attemps += 1
|
|
31
34
|
return state_machine.return_home_failed # type: ignore
|
|
32
|
-
|
|
35
|
+
|
|
36
|
+
if not state_machine.battery_level_is_above_mission_start_threshold():
|
|
37
|
+
return state_machine.starting_recharging # type: ignore
|
|
38
|
+
else:
|
|
39
|
+
return state_machine.returned_home # type: ignore
|
|
33
40
|
|
|
34
41
|
def _start_mission_event_handler(
|
|
35
42
|
event: Event[Mission],
|
|
36
43
|
) -> Optional[Callable]:
|
|
37
44
|
if event.has_event():
|
|
45
|
+
if not state_machine.battery_level_is_above_mission_start_threshold():
|
|
46
|
+
try:
|
|
47
|
+
state_machine.events.api_requests.start_mission.response.trigger_event(
|
|
48
|
+
MissionStartResponse(
|
|
49
|
+
mission_id=None,
|
|
50
|
+
mission_started=False,
|
|
51
|
+
mission_not_started_reason="Robot battery too low",
|
|
52
|
+
),
|
|
53
|
+
timeout=1, # This conflict can happen if two API requests are received at the same time
|
|
54
|
+
)
|
|
55
|
+
except EventTimeoutError:
|
|
56
|
+
pass
|
|
57
|
+
return None
|
|
38
58
|
return state_machine.stop # type: ignore
|
|
39
59
|
return None
|
|
40
60
|
|
|
@@ -23,7 +23,9 @@ class RobotStandingStill(EventHandlerBase):
|
|
|
23
23
|
EventHandlerMapping(
|
|
24
24
|
name="start_mission_event",
|
|
25
25
|
event=events.api_requests.start_mission.request,
|
|
26
|
-
handler=lambda event: start_mission_event_handler(
|
|
26
|
+
handler=lambda event: start_mission_event_handler(
|
|
27
|
+
state_machine, event, events.api_requests.start_mission.response
|
|
28
|
+
),
|
|
27
29
|
),
|
|
28
30
|
EventHandlerMapping(
|
|
29
31
|
name="return_home_event",
|
|
@@ -5,8 +5,9 @@ if TYPE_CHECKING:
|
|
|
5
5
|
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
from isar.config.settings import settings
|
|
8
9
|
from robot_interface.models.mission.mission import Mission
|
|
9
|
-
from robot_interface.models.mission.status import MissionStatus, TaskStatus
|
|
10
|
+
from robot_interface.models.mission.status import MissionStatus, RobotStatus, TaskStatus
|
|
10
11
|
from robot_interface.models.mission.task import ReturnToHome
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
@@ -23,6 +24,21 @@ def start_return_home_mission(state_machine: "StateMachine") -> bool:
|
|
|
23
24
|
return True
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
def should_retry_return_home(state_machine: "StateMachine") -> bool:
|
|
28
|
+
if state_machine.shared_state.robot_status.check() != RobotStatus.Available:
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
state_machine.returning_home_state.failed_return_home_attemps
|
|
33
|
+
< settings.RETURN_HOME_RETRY_LIMIT
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def reset_return_home_failure_counter(state_machine: "StateMachine") -> bool:
|
|
38
|
+
state_machine.returning_home_state.failed_return_home_attemps = 0
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
|
|
26
42
|
def set_return_home_status(state_machine: "StateMachine") -> bool:
|
|
27
43
|
state_machine.log_mission_overview(mission=state_machine.current_mission)
|
|
28
44
|
state_machine.current_mission.status = MissionStatus.InProgress
|
|
@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
|
|
|
3
3
|
if TYPE_CHECKING:
|
|
4
4
|
from isar.state_machine.state_machine import StateMachine
|
|
5
5
|
|
|
6
|
+
from isar.apis.models.models import MissionStartResponse
|
|
6
7
|
from robot_interface.models.exceptions.robot_exceptions import (
|
|
7
8
|
ErrorMessage,
|
|
8
9
|
RobotException,
|
|
@@ -12,7 +13,9 @@ from robot_interface.models.mission.status import MissionStatus, TaskStatus
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def acknowledge_mission(state_machine: "StateMachine") -> bool:
|
|
15
|
-
state_machine.events.api_requests.start_mission.response.put(
|
|
16
|
+
state_machine.events.api_requests.start_mission.response.put(
|
|
17
|
+
MissionStartResponse(mission_started=True)
|
|
18
|
+
)
|
|
16
19
|
return True
|
|
17
20
|
|
|
18
21
|
|
|
@@ -70,5 +73,10 @@ def trigger_start_mission_event(state_machine: "StateMachine") -> bool:
|
|
|
70
73
|
|
|
71
74
|
|
|
72
75
|
def _initialization_failed(state_machine: "StateMachine") -> None:
|
|
73
|
-
state_machine.events.api_requests.start_mission.response.put(
|
|
76
|
+
state_machine.events.api_requests.start_mission.response.put(
|
|
77
|
+
MissionStartResponse(
|
|
78
|
+
mission_started=False,
|
|
79
|
+
mission_not_started_reason="Failed to initialize robot",
|
|
80
|
+
)
|
|
81
|
+
)
|
|
74
82
|
state_machine._finalize()
|
|
@@ -5,8 +5,10 @@ from isar.state_machine.transitions.functions.fail_mission import (
|
|
|
5
5
|
report_failed_return_home_and_intervention_needed,
|
|
6
6
|
)
|
|
7
7
|
from isar.state_machine.transitions.functions.return_home import (
|
|
8
|
+
reset_return_home_failure_counter,
|
|
8
9
|
return_home_finished,
|
|
9
10
|
set_return_home_status,
|
|
11
|
+
should_retry_return_home,
|
|
10
12
|
start_return_home_mission,
|
|
11
13
|
)
|
|
12
14
|
from isar.state_machine.transitions.functions.start_mission import (
|
|
@@ -28,12 +30,14 @@ def get_return_home_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
28
30
|
state_machine.home_state,
|
|
29
31
|
state_machine.robot_standing_still_state,
|
|
30
32
|
state_machine.intervention_needed_state,
|
|
33
|
+
state_machine.monitor_state,
|
|
31
34
|
],
|
|
32
35
|
"dest": state_machine.returning_home_state,
|
|
33
36
|
"conditions": [
|
|
34
37
|
def_transition(state_machine, start_return_home_mission),
|
|
35
38
|
def_transition(state_machine, set_return_home_status),
|
|
36
39
|
def_transition(state_machine, initialize_robot),
|
|
40
|
+
def_transition(state_machine, initialize_robot),
|
|
37
41
|
],
|
|
38
42
|
"before": def_transition(state_machine, trigger_start_mission_event),
|
|
39
43
|
},
|
|
@@ -59,7 +63,34 @@ def get_return_home_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
59
63
|
"trigger": "returned_home",
|
|
60
64
|
"source": state_machine.returning_home_state,
|
|
61
65
|
"dest": state_machine.home_state,
|
|
62
|
-
"before":
|
|
66
|
+
"before": [
|
|
67
|
+
def_transition(state_machine, reset_return_home_failure_counter),
|
|
68
|
+
def_transition(state_machine, return_home_finished),
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"trigger": "starting_recharging",
|
|
73
|
+
"source": state_machine.returning_home_state,
|
|
74
|
+
"dest": state_machine.recharging_state,
|
|
75
|
+
"before": [
|
|
76
|
+
def_transition(state_machine, reset_return_home_failure_counter),
|
|
77
|
+
def_transition(state_machine, return_home_finished),
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"trigger": "return_home_failed",
|
|
82
|
+
"source": state_machine.returning_home_state,
|
|
83
|
+
"dest": state_machine.returning_home_state,
|
|
84
|
+
"conditions": [
|
|
85
|
+
def_transition(state_machine, should_retry_return_home),
|
|
86
|
+
],
|
|
87
|
+
"before": [
|
|
88
|
+
def_transition(state_machine, report_failed_mission_and_finalize),
|
|
89
|
+
def_transition(state_machine, start_return_home_mission),
|
|
90
|
+
def_transition(state_machine, set_return_home_status),
|
|
91
|
+
def_transition(state_machine, initialize_robot),
|
|
92
|
+
def_transition(state_machine, trigger_start_mission_event),
|
|
93
|
+
],
|
|
63
94
|
},
|
|
64
95
|
{
|
|
65
96
|
"trigger": "return_home_failed",
|
|
@@ -69,6 +100,7 @@ def get_return_home_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
69
100
|
def_transition(
|
|
70
101
|
state_machine, report_failed_return_home_and_intervention_needed
|
|
71
102
|
),
|
|
103
|
+
def_transition(state_machine, reset_return_home_failure_counter),
|
|
72
104
|
def_transition(state_machine, report_failed_mission_and_finalize),
|
|
73
105
|
],
|
|
74
106
|
},
|
|
@@ -69,5 +69,15 @@ def get_robot_status_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
69
69
|
],
|
|
70
70
|
"dest": state_machine.unknown_status_state,
|
|
71
71
|
},
|
|
72
|
+
{
|
|
73
|
+
"trigger": "robot_went_offline",
|
|
74
|
+
"source": [state_machine.recharging_state],
|
|
75
|
+
"dest": state_machine.offline_state,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"trigger": "robot_recharged",
|
|
79
|
+
"source": [state_machine.recharging_state],
|
|
80
|
+
"dest": state_machine.home_state,
|
|
81
|
+
},
|
|
72
82
|
]
|
|
73
83
|
return robot_status_transitions
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, Callable, Optional
|
|
2
2
|
|
|
3
|
-
from isar.apis.models.models import ControlMissionResponse
|
|
4
|
-
from isar.models.events import Event
|
|
3
|
+
from isar.apis.models.models import ControlMissionResponse, MissionStartResponse
|
|
4
|
+
from isar.models.events import Event, EventTimeoutError
|
|
5
5
|
from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
|
|
6
6
|
from robot_interface.models.mission.mission import Mission
|
|
7
7
|
from robot_interface.models.mission.status import RobotStatus, TaskStatus
|
|
@@ -11,10 +11,25 @@ if TYPE_CHECKING:
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def start_mission_event_handler(
|
|
14
|
-
state_machine: "StateMachine",
|
|
14
|
+
state_machine: "StateMachine",
|
|
15
|
+
event: Event[Mission],
|
|
16
|
+
response: Event[MissionStartResponse],
|
|
15
17
|
) -> Optional[Callable]:
|
|
16
18
|
mission: Optional[Mission] = event.consume_event()
|
|
17
19
|
if mission:
|
|
20
|
+
if not state_machine.battery_level_is_above_mission_start_threshold():
|
|
21
|
+
try:
|
|
22
|
+
response.trigger_event(
|
|
23
|
+
MissionStartResponse(
|
|
24
|
+
mission_id=mission.id,
|
|
25
|
+
mission_started=False,
|
|
26
|
+
mission_not_started_reason="Robot battery too low",
|
|
27
|
+
),
|
|
28
|
+
timeout=1, # This conflict can happen if two API requests are received at the same time
|
|
29
|
+
)
|
|
30
|
+
except EventTimeoutError:
|
|
31
|
+
pass
|
|
32
|
+
return None
|
|
18
33
|
state_machine.start_mission(mission=mission)
|
|
19
34
|
return state_machine.request_mission_start # type: ignore
|
|
20
35
|
return None
|
|
@@ -4,7 +4,7 @@ isar/script.py,sha256=LCb7CHvNyZhQz1OaQ-BUX8w7RDmu975zA0kL6FPwfzk,5912
|
|
|
4
4
|
isar/apis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
isar/apis/api.py,sha256=VM_WBlHJ4Juv9A0FvaUTeaMVYCN6a7pAojxIGCmDtf0,14154
|
|
6
6
|
isar/apis/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
isar/apis/models/models.py,sha256=
|
|
7
|
+
isar/apis/models/models.py,sha256=iRyqflpjGopm4KJjgRrOnC2swNEk3a-iwVynzw3Nm6c,1992
|
|
8
8
|
isar/apis/models/start_mission_definition.py,sha256=v-wt1XDd53Sw7DCdFAkxBBut-xd_uGJa7qMJnE3VI9k,6364
|
|
9
9
|
isar/apis/robot_control/robot_controller.py,sha256=0EHjMqOBdfJlMrCItGPz5X-4X2kc-2XlNnUU2LOcLfc,1219
|
|
10
10
|
isar/apis/schedule/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -15,8 +15,8 @@ isar/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
15
15
|
isar/config/configuration_error.py,sha256=rO6WOhafX6xvVib8WxV-eY483Z0PpN-9PxGsq5ATfKc,46
|
|
16
16
|
isar/config/log.py,sha256=f_mLLz5RSa0kZkdpi1m0iMdwwDc4RQp12mnT6gu2exE,1303
|
|
17
17
|
isar/config/logging.conf,sha256=a7ZBvZkrMDaPU3eRGAjL_eZz6hZsa6BaRJOfx8mbnnM,629
|
|
18
|
-
isar/config/open_telemetry.py,sha256=
|
|
19
|
-
isar/config/settings.py,sha256=
|
|
18
|
+
isar/config/open_telemetry.py,sha256=Lgu0lbRQA-zz6ZDoBKKk0whQex5w18jl1wjqWzHUGdg,3634
|
|
19
|
+
isar/config/settings.py,sha256=fUEsKeCwxWg1kMb3rlOgmIyw0V_ia9o-sqfXq2KMovA,14040
|
|
20
20
|
isar/config/certs/ca-cert.pem,sha256=qoNljfad_qcMxhXJIUMLd7nT-Qwf_d4dYSdoOFEOE8I,2179
|
|
21
21
|
isar/config/keyvault/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
isar/config/keyvault/keyvault_error.py,sha256=zvPCsZLjboxsxthYkxpRERCTFxYV8R5WmACewAUQLwk,41
|
|
@@ -35,17 +35,17 @@ isar/config/predefined_mission_definition/default_turtlebot.json,sha256=20ee7q1E
|
|
|
35
35
|
isar/config/predefined_missions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
isar/config/predefined_missions/default.json,sha256=NWo9y5noPmpjlNUxLnZK95Sz7DIEaUR-ISYlw3MP8i0,1251
|
|
37
37
|
isar/config/predefined_missions/default_turtlebot.json,sha256=8Vk1_0P0BBsG0vwh4vwIYINiiWioErHZ0Ppjq3ctaPM,2143
|
|
38
|
-
isar/eventhandlers/eventhandler.py,sha256=
|
|
38
|
+
isar/eventhandlers/eventhandler.py,sha256=Wu4IsJA-k0I1F41q7qDT_QJl_OaUHNeGgzPufv8q5wo,3507
|
|
39
39
|
isar/mission_planner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
40
|
isar/mission_planner/local_planner.py,sha256=Mkg3vvUBF1jImfQnaFvXLNpKVadR21X4mwDd_wHqJ2w,2520
|
|
41
41
|
isar/mission_planner/mission_planner_interface.py,sha256=UgpPIM4FbrWOD7fGY3Ul64k3uYb8wo0FwSWGewYoVbc,485
|
|
42
42
|
isar/mission_planner/sequential_task_selector.py,sha256=66agRPHuJnEa1vArPyty4muTasAZ50XPjjrSaTdY_Cc,643
|
|
43
43
|
isar/mission_planner/task_selector_interface.py,sha256=pnLeaGPIuyXThcflZ_A7YL2b2xQjFT88hAZidkMomxU,707
|
|
44
44
|
isar/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
-
isar/models/events.py,sha256=
|
|
45
|
+
isar/models/events.py,sha256=7xawOqXnM48JhTGIaKz7tiTjIMauMfyJZUd68jEgzdo,4194
|
|
46
46
|
isar/robot/robot.py,sha256=keqsYmfRlyIC4CWkMG0erDt_4uIC0IPvyG2ni4MemyI,5350
|
|
47
47
|
isar/robot/robot_start_mission.py,sha256=RPYH9VtXDFdPqhOpt6kSbT17RHkJQPKkQ6d4784_pFE,3210
|
|
48
|
-
isar/robot/robot_status.py,sha256=
|
|
48
|
+
isar/robot/robot_status.py,sha256=OXx18Qj0bvb2sKzn5-yKXWGdZ9GYyCOIgiTaGof-0Wc,2055
|
|
49
49
|
isar/robot/robot_stop_mission.py,sha256=4GZyhLU0hB6ZK0ArCVhFS9Sf3ZbqFI9DvPNfvD889Ps,2270
|
|
50
50
|
isar/robot/robot_task_status.py,sha256=jefIDfrbly7vWZztWA2zLmK5Yz1NSEytw2YUmprccNA,3161
|
|
51
51
|
isar/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -59,55 +59,56 @@ isar/services/service_connections/mqtt/robot_heartbeat_publisher.py,sha256=_bUOG
|
|
|
59
59
|
isar/services/service_connections/mqtt/robot_info_publisher.py,sha256=AxokGk51hRPTxxD2r0P9braPJCMrf1InaCxrUBKkF4g,1402
|
|
60
60
|
isar/services/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
61
61
|
isar/services/utilities/robot_utilities.py,sha256=4zCigsLXfqDC8POHchktSq81zr1_pTaRve_LQsVr6Mk,514
|
|
62
|
-
isar/services/utilities/scheduling_utilities.py,sha256=
|
|
62
|
+
isar/services/utilities/scheduling_utilities.py,sha256=G3SgWUIMAmAmdfbFd1oeUtRVOZEClE0wugdldEEwN_s,13499
|
|
63
63
|
isar/services/utilities/threaded_request.py,sha256=py4G-_RjnIdHljmKFAcQ6ddqMmp-ZYV39Ece-dqRqjs,1874
|
|
64
64
|
isar/state_machine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
|
-
isar/state_machine/state_machine.py,sha256=
|
|
66
|
-
isar/state_machine/states_enum.py,sha256=
|
|
65
|
+
isar/state_machine/state_machine.py,sha256=VvArtwOkvA63WMVAeW2WVHvxWse_qSok1RjustPYJgk,18028
|
|
66
|
+
isar/state_machine/states_enum.py,sha256=4Kysag9JPi2JSyC2me2B1sLH3Sfo7qGJonc-Q_IiUug,511
|
|
67
67
|
isar/state_machine/states/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
|
-
isar/state_machine/states/await_next_mission.py,sha256=
|
|
68
|
+
isar/state_machine/states/await_next_mission.py,sha256=yg0aH4eukcnE1xRKiPVafRxqHt3WdvqD08EATzdEojk,1961
|
|
69
69
|
isar/state_machine/states/blocked_protective_stop.py,sha256=GEOvxnHXjpCsrh3aa8Idtn1zMYv5HLQzj9qQAq8HvaU,1180
|
|
70
|
-
isar/state_machine/states/home.py,sha256=
|
|
70
|
+
isar/state_machine/states/home.py,sha256=TYv7WF5yX31Zw6Cn5MY2GHebUfypL5w510I92BEi_vE,1950
|
|
71
71
|
isar/state_machine/states/intervention_needed.py,sha256=A0QYz1vD1tDKAealihXDuoGuMLtXrRfn_AwFoaUhT_Q,1640
|
|
72
|
-
isar/state_machine/states/monitor.py,sha256=
|
|
72
|
+
isar/state_machine/states/monitor.py,sha256=_Y6FFsuUEzEveH4skKAE8Cf6cie4keXr_LGA6gbUlQg,4741
|
|
73
73
|
isar/state_machine/states/offline.py,sha256=5wdNzC1wG0cWrpuT_r_mk8UNFHA4QSyg8ejXUTAg4Io,1137
|
|
74
74
|
isar/state_machine/states/paused.py,sha256=osmLZhm0-2jcZmNRauveeidCBYayDZvLy-1TNYv6CgQ,1091
|
|
75
|
-
isar/state_machine/states/
|
|
76
|
-
isar/state_machine/states/
|
|
75
|
+
isar/state_machine/states/recharging.py,sha256=ni5WqFU0VDhdGC3ZfHcabSICm_5kwaS72wc3ecAB_os,1651
|
|
76
|
+
isar/state_machine/states/returning_home.py,sha256=B57eFsMOMTbYH1IwbLLnhhMy-SaeGLnrXH1oY8V5Vk8,4508
|
|
77
|
+
isar/state_machine/states/robot_standing_still.py,sha256=EWG2_IPbxK6HFERHcmMFlWdN5PnXJXjwEfNlpsJnPkQ,1985
|
|
77
78
|
isar/state_machine/states/stopping.py,sha256=fMXXvcXtzML-Q5A-uRD_gXePy51fnt9jhOTVFN-3qqA,2420
|
|
78
79
|
isar/state_machine/states/unknown_status.py,sha256=Y-g9cgme5zriyZ6YUzFRYhOvv9ZylCSwfaY8MJ7xEJ0,1791
|
|
79
80
|
isar/state_machine/transitions/mission.py,sha256=mUOtEyK2UX_kdK-_L4wq5a60882H2KnlxImO72YMtuo,5557
|
|
80
|
-
isar/state_machine/transitions/return_home.py,sha256=
|
|
81
|
-
isar/state_machine/transitions/robot_status.py,sha256=
|
|
81
|
+
isar/state_machine/transitions/return_home.py,sha256=E14fZR4RhEBdLaeeKPbRYXb2Y36zus7fXoVT_02PiEI,4649
|
|
82
|
+
isar/state_machine/transitions/robot_status.py,sha256=sALt9BwZUnIFmVe35N1ptC-PyhfdHiTGu1R0GzpAQXk,3056
|
|
82
83
|
isar/state_machine/transitions/functions/fail_mission.py,sha256=jHHXhfQVYQEzCXgTEhv5e6uEK9p6iDPFFXqS9bzs_-A,720
|
|
83
84
|
isar/state_machine/transitions/functions/finish_mission.py,sha256=TRQrk7HdllmAkwsp25HRZAFAk46Y1hLx3jmkIAKrHDI,1442
|
|
84
85
|
isar/state_machine/transitions/functions/pause.py,sha256=gWr-s76gL0-otrZxCBNmTxUicrAVsdNWDQmHSAOvs6E,1977
|
|
85
86
|
isar/state_machine/transitions/functions/resume.py,sha256=ji4GjQvQQCr_jYRUGYHSYt-vePtO2z7VAUeKF-hyLBw,2106
|
|
86
|
-
isar/state_machine/transitions/functions/return_home.py,sha256=
|
|
87
|
+
isar/state_machine/transitions/functions/return_home.py,sha256=5WPO40MtuRKm9-NtyrS6m0IVEit14MXfMKjgZ2sCXRU,1666
|
|
87
88
|
isar/state_machine/transitions/functions/robot_status.py,sha256=P1Cu8xVysbiKRKL4E8mSyoL2-72HfxrLvvOcdnBOlvw,889
|
|
88
|
-
isar/state_machine/transitions/functions/start_mission.py,sha256=
|
|
89
|
+
isar/state_machine/transitions/functions/start_mission.py,sha256=dUNy_EvkdR9Ud9PHwAaew9pzpdyus6n-X_F2Jwv1X7c,2834
|
|
89
90
|
isar/state_machine/transitions/functions/stop.py,sha256=ZG33TK3VuJwT-LAQ0a3thgt714yCIu42_bo-u1-V1J0,2956
|
|
90
91
|
isar/state_machine/transitions/functions/utils.py,sha256=Wa72Ocq4QT1E6qkpEJZQ3h5o33pGvx7Tlkt2JZ2Grbk,314
|
|
91
|
-
isar/state_machine/utils/common_event_handlers.py,sha256=
|
|
92
|
+
isar/state_machine/utils/common_event_handlers.py,sha256=0rQJM-HWGjaSc_60NYcEkJFMzEWCBSYC7i8IHrz1S9M,6447
|
|
92
93
|
isar/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
93
94
|
isar/storage/blob_storage.py,sha256=QueyHzCJfUUyOLxBeYwK0Ot7w_CYakBEhtTtITelKAo,3026
|
|
94
95
|
isar/storage/local_storage.py,sha256=-9Bz2WmniLKwKMeAdrgoMzkj781AOLIUsfaqQERqpUk,1985
|
|
95
96
|
isar/storage/storage_interface.py,sha256=DRIiy0mRZL3KMZysG0R2Cque6hoqd5haZBw11WM53Vc,840
|
|
96
97
|
isar/storage/uploader.py,sha256=uD1DzvJ2yYtNAwQGa7UD7kNOxZfKxJ1cCdi7sfOVZ10,9443
|
|
97
98
|
isar/storage/utilities.py,sha256=oLH0Rp7UtrQQdilfITnmXO1Z0ExdeDhBImYHid55vBA,3449
|
|
98
|
-
isar-1.
|
|
99
|
+
isar-1.33.0.dist-info/licenses/LICENSE,sha256=3fc2-ebLwHWwzfQbulGNRdcNob3SBQeCfEVUDYxsuqw,14058
|
|
99
100
|
robot_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
100
|
-
robot_interface/robot_interface.py,sha256
|
|
101
|
+
robot_interface/robot_interface.py,sha256=A6t19lcNU_6AfkYKY5DaF0sheym_SZEAawbfaj36Kjk,8997
|
|
101
102
|
robot_interface/test_robot_interface.py,sha256=FV1urn7SbsMyWBIcTKjsBwAG4IsXeZ6pLHE0mA9EGGs,692
|
|
102
103
|
robot_interface/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
103
104
|
robot_interface/models/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
105
|
robot_interface/models/exceptions/robot_exceptions.py,sha256=VrsWPf4g0qN5sJRKhc3ER_Wt5drK0MZRuECU-csIlDA,10026
|
|
105
106
|
robot_interface/models/initialize/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
106
107
|
robot_interface/models/inspection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
107
|
-
robot_interface/models/inspection/inspection.py,sha256=
|
|
108
|
+
robot_interface/models/inspection/inspection.py,sha256=cjAvekL8r82s7bgukWeXpYylHvJG_oRSCJNISPVCvZg,2238
|
|
108
109
|
robot_interface/models/mission/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
109
110
|
robot_interface/models/mission/mission.py,sha256=MQ9p5cuclLXexaZu9bkDh5-aN99eunvYC0vP-Z_kUwI,960
|
|
110
|
-
robot_interface/models/mission/status.py,sha256=
|
|
111
|
+
robot_interface/models/mission/status.py,sha256=KC-79HBjM1-6xA0WmwAtZl8QC0pZu6u4wbYWb0IjGd8,816
|
|
111
112
|
robot_interface/models/mission/task.py,sha256=YzaqJ_KIIm-3S2Y2-BG4Pdkc8sjFMzMx5jj8FtXSmFg,4744
|
|
112
113
|
robot_interface/models/robots/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
113
114
|
robot_interface/models/robots/battery_state.py,sha256=ktOtJ8ltdK0k_i7BoqYfhc5dbOzIG6Oo-uWC67fCWio,98
|
|
@@ -115,12 +116,12 @@ robot_interface/models/robots/media.py,sha256=8A-CuuubfngzPprs6zWB9hSaqe3jzgsE8r
|
|
|
115
116
|
robot_interface/models/robots/robot_model.py,sha256=-0jNKWPcEgtF_2klb1It3u0SCoAR0hSW9nce58Zq0Co,417
|
|
116
117
|
robot_interface/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
117
118
|
robot_interface/telemetry/mqtt_client.py,sha256=ueXdtIFNCwciTj4spvdJj9emd-IOmUuJjpsXQSSWZPY,2987
|
|
118
|
-
robot_interface/telemetry/payloads.py,sha256=
|
|
119
|
+
robot_interface/telemetry/payloads.py,sha256=T7EK-b0bVhADDTKMIAHmcPVtPxuR16csmZoOJQl9P4c,3103
|
|
119
120
|
robot_interface/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
120
121
|
robot_interface/utilities/json_service.py,sha256=qkzVkb60Gi_pto-b5n1vNzCrQze2yqgIJqSLNLYj1Fg,1034
|
|
121
122
|
robot_interface/utilities/uuid_string_factory.py,sha256=_NQIbBQ56w0qqO0MUDP6aPpHbxW7ATRhK8HnQiBSLkc,76
|
|
122
|
-
isar-1.
|
|
123
|
-
isar-1.
|
|
124
|
-
isar-1.
|
|
125
|
-
isar-1.
|
|
126
|
-
isar-1.
|
|
123
|
+
isar-1.33.0.dist-info/METADATA,sha256=T0cbwIlPcWsZ0jwsQbKTLZBo3BI2FTlXq_LwoLKcbdQ,31217
|
|
124
|
+
isar-1.33.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
125
|
+
isar-1.33.0.dist-info/entry_points.txt,sha256=TFam7uNNw7J0iiDYzsH2gfG0u1eV1wh3JTw_HkhgKLk,49
|
|
126
|
+
isar-1.33.0.dist-info/top_level.txt,sha256=UwIML2RtuQKCyJJkatcSnyp6-ldDjboB9k9JgKipO-U,21
|
|
127
|
+
isar-1.33.0.dist-info/RECORD,,
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
from dataclasses import dataclass, field
|
|
3
1
|
from datetime import datetime
|
|
4
2
|
from typing import Optional, Type
|
|
5
3
|
|
|
@@ -7,42 +5,35 @@ from alitra import Pose, Position
|
|
|
7
5
|
from pydantic import BaseModel, Field
|
|
8
6
|
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
class InspectionMetadata(ABC):
|
|
8
|
+
class InspectionMetadata(BaseModel):
|
|
12
9
|
start_time: datetime
|
|
13
10
|
robot_pose: Pose
|
|
14
11
|
target_position: Position
|
|
15
12
|
file_type: str
|
|
16
|
-
tag_id: Optional[str] =
|
|
17
|
-
inspection_description: Optional[str] =
|
|
13
|
+
tag_id: Optional[str] = None
|
|
14
|
+
inspection_description: Optional[str] = None
|
|
18
15
|
|
|
19
16
|
|
|
20
|
-
@dataclass
|
|
21
17
|
class ImageMetadata(InspectionMetadata):
|
|
22
18
|
pass
|
|
23
19
|
|
|
24
20
|
|
|
25
|
-
@dataclass
|
|
26
21
|
class ThermalImageMetadata(InspectionMetadata):
|
|
27
22
|
pass
|
|
28
23
|
|
|
29
24
|
|
|
30
|
-
@dataclass
|
|
31
25
|
class VideoMetadata(InspectionMetadata):
|
|
32
|
-
duration:
|
|
26
|
+
duration: float
|
|
33
27
|
|
|
34
28
|
|
|
35
|
-
@dataclass
|
|
36
29
|
class ThermalVideoMetadata(InspectionMetadata):
|
|
37
|
-
duration:
|
|
30
|
+
duration: float
|
|
38
31
|
|
|
39
32
|
|
|
40
|
-
@dataclass
|
|
41
33
|
class AudioMetadata(InspectionMetadata):
|
|
42
|
-
duration:
|
|
34
|
+
duration: float
|
|
43
35
|
|
|
44
36
|
|
|
45
|
-
@dataclass
|
|
46
37
|
class GasMeasurementMetadata(InspectionMetadata):
|
|
47
38
|
pass
|
|
48
39
|
|
|
@@ -244,3 +244,30 @@ class RobotInterface(metaclass=ABCMeta):
|
|
|
244
244
|
|
|
245
245
|
"""
|
|
246
246
|
raise NotImplementedError
|
|
247
|
+
|
|
248
|
+
@abstractmethod
|
|
249
|
+
def get_battery_level(self) -> float:
|
|
250
|
+
"""
|
|
251
|
+
Method which returns the percent charge remaining in the robot battery.
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
float
|
|
256
|
+
The battery percentage on the robot
|
|
257
|
+
|
|
258
|
+
Raises
|
|
259
|
+
-------
|
|
260
|
+
RobotCommunicationException
|
|
261
|
+
Raised if the robot package is unable to communicate with the robot API.
|
|
262
|
+
The fetching of robot battery level will be attempted again until success
|
|
263
|
+
RobotAPIException
|
|
264
|
+
Raised if the robot package is able to communicate with the API but an error
|
|
265
|
+
occurred while interpreting the response. The fetching of robot battery level
|
|
266
|
+
will be attempted again until success
|
|
267
|
+
RobotException
|
|
268
|
+
Catches other RobotExceptions that may have occurred while retrieving the
|
|
269
|
+
robot battery level. The fetching of robot battery level will
|
|
270
|
+
be attempted again until success
|
|
271
|
+
|
|
272
|
+
"""
|
|
273
|
+
raise NotImplementedError
|
|
@@ -91,6 +91,16 @@ class MissionPayload:
|
|
|
91
91
|
timestamp: datetime
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
@dataclass
|
|
95
|
+
class MissionAbortedPayload:
|
|
96
|
+
isar_id: str
|
|
97
|
+
robot_name: str
|
|
98
|
+
mission_id: str
|
|
99
|
+
can_be_continued: bool
|
|
100
|
+
timestamp: datetime
|
|
101
|
+
reason: Optional[str]
|
|
102
|
+
|
|
103
|
+
|
|
94
104
|
@dataclass
|
|
95
105
|
class TaskPayload:
|
|
96
106
|
isar_id: str
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|