isar 1.26.3__py3-none-any.whl → 1.27.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/api.py +25 -0
- isar/apis/models/start_mission_definition.py +1 -7
- isar/apis/schedule/scheduling_controller.py +17 -1
- isar/config/settings.py +4 -1
- isar/models/communication/queues/events.py +1 -0
- isar/models/communication/queues/queue_utils.py +6 -0
- isar/modules.py +13 -6
- isar/robot/robot.py +10 -1
- isar/robot/robot_status.py +11 -5
- isar/services/utilities/scheduling_utilities.py +60 -7
- isar/state_machine/state_machine.py +51 -121
- isar/state_machine/states/await_next_mission.py +92 -0
- isar/state_machine/states/blocked_protective_stop.py +2 -5
- isar/state_machine/states/home.py +87 -0
- isar/state_machine/states/monitor.py +8 -3
- isar/state_machine/states/offline.py +3 -5
- isar/state_machine/states/returning_home.py +186 -0
- isar/state_machine/states/{idle.py → robot_standing_still.py} +20 -8
- isar/state_machine/states/{stop.py → stopping.py} +16 -4
- isar/state_machine/states/unknown_status.py +74 -0
- isar/state_machine/states_enum.py +6 -3
- isar/state_machine/transitions/functions/return_home.py +38 -0
- isar/state_machine/transitions/functions/robot_status.py +27 -0
- isar/state_machine/transitions/{start_mission.py → functions/start_mission.py} +1 -1
- isar/state_machine/transitions/mission.py +119 -0
- isar/state_machine/transitions/return_home.py +69 -0
- isar/state_machine/transitions/robot_status.py +73 -0
- {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/METADATA +14 -7
- {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/RECORD +43 -35
- {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/WHEEL +1 -1
- robot_interface/models/exceptions/robot_exceptions.py +14 -0
- robot_interface/models/mission/mission.py +8 -1
- robot_interface/models/mission/status.py +2 -0
- robot_interface/models/mission/task.py +0 -1
- isar/state_machine/states/off.py +0 -18
- /isar/state_machine/transitions/{fail_mission.py → functions/fail_mission.py} +0 -0
- /isar/state_machine/transitions/{finish_mission.py → functions/finish_mission.py} +0 -0
- /isar/state_machine/transitions/{pause.py → functions/pause.py} +0 -0
- /isar/state_machine/transitions/{resume.py → functions/resume.py} +0 -0
- /isar/state_machine/transitions/{stop.py → functions/stop.py} +0 -0
- /isar/state_machine/transitions/{utils.py → functions/utils.py} +0 -0
- {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/entry_points.txt +0 -0
- {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/licenses/LICENSE +0 -0
- {isar-1.26.3.dist-info → isar-1.27.0.dist-info}/top_level.txt +0 -0
isar/apis/api.py
CHANGED
|
@@ -150,6 +150,31 @@ class API:
|
|
|
150
150
|
HTTPStatus.CONFLICT.value: {
|
|
151
151
|
"description": "Conflict - Invalid command in the current state",
|
|
152
152
|
},
|
|
153
|
+
HTTPStatus.BAD_REQUEST.value: {
|
|
154
|
+
"description": "Bad request - Robot does not have the capabilities to perform the mission",
|
|
155
|
+
},
|
|
156
|
+
HTTPStatus.INTERNAL_SERVER_ERROR.value: {
|
|
157
|
+
"description": "Internal Server Error - Current state of state machine unknown",
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
router.add_api_route(
|
|
162
|
+
path="/schedule/return-home",
|
|
163
|
+
endpoint=self.scheduling_controller.return_home,
|
|
164
|
+
methods=["POST"],
|
|
165
|
+
dependencies=[authentication_dependency],
|
|
166
|
+
summary="Start return home mission",
|
|
167
|
+
responses={
|
|
168
|
+
HTTPStatus.OK.value: {
|
|
169
|
+
"description": "Return home mission succesfully started",
|
|
170
|
+
"model": StartMissionResponse,
|
|
171
|
+
},
|
|
172
|
+
HTTPStatus.UNPROCESSABLE_ENTITY.value: {
|
|
173
|
+
"description": "Invalid body - The JSON is incorrect",
|
|
174
|
+
},
|
|
175
|
+
HTTPStatus.CONFLICT.value: {
|
|
176
|
+
"description": "Conflict - Invalid command in the current state",
|
|
177
|
+
},
|
|
153
178
|
HTTPStatus.INTERNAL_SERVER_ERROR.value: {
|
|
154
179
|
"description": "Internal Server Error - Current state of state machine unknown",
|
|
155
180
|
},
|
|
@@ -92,7 +92,7 @@ def to_isar_task(task_definition: StartMissionTaskDefinition) -> TASKS:
|
|
|
92
92
|
if task_definition.type == TaskType.Inspection:
|
|
93
93
|
return to_inspection_task(task_definition)
|
|
94
94
|
elif task_definition.type == TaskType.ReturnToHome:
|
|
95
|
-
return
|
|
95
|
+
return ReturnToHome()
|
|
96
96
|
else:
|
|
97
97
|
raise MissionPlannerError(
|
|
98
98
|
f"Failed to create task: '{task_definition.type}' is not a valid"
|
|
@@ -162,11 +162,5 @@ def to_inspection_task(task_definition: StartMissionTaskDefinition) -> TASKS:
|
|
|
162
162
|
)
|
|
163
163
|
|
|
164
164
|
|
|
165
|
-
def create_return_to_home_task(
|
|
166
|
-
task_definition: StartMissionTaskDefinition,
|
|
167
|
-
) -> ReturnToHome:
|
|
168
|
-
return ReturnToHome(pose=task_definition.pose.to_alitra_pose())
|
|
169
|
-
|
|
170
|
-
|
|
171
165
|
def _build_mission_name() -> str:
|
|
172
166
|
return f"{settings.PLANT_SHORT_NAME}{settings.ROBOT_NAME}{int(time.time())}"
|
|
@@ -98,6 +98,16 @@ class SchedulingController:
|
|
|
98
98
|
self.scheduling_utilities.start_mission(mission=mission)
|
|
99
99
|
return self._api_response(mission)
|
|
100
100
|
|
|
101
|
+
def return_home(self) -> None:
|
|
102
|
+
self.logger.info("Received request to return home")
|
|
103
|
+
|
|
104
|
+
state: States = self.scheduling_utilities.get_state()
|
|
105
|
+
self.scheduling_utilities.verify_state_machine_ready_to_receive_return_home_mission(
|
|
106
|
+
state
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
self.scheduling_utilities.return_home()
|
|
110
|
+
|
|
101
111
|
def pause_mission(self) -> ControlMissionResponse:
|
|
102
112
|
self.logger.info("Received request to pause current mission")
|
|
103
113
|
|
|
@@ -142,7 +152,13 @@ class SchedulingController:
|
|
|
142
152
|
|
|
143
153
|
state: States = self.scheduling_utilities.get_state()
|
|
144
154
|
|
|
145
|
-
if
|
|
155
|
+
if (
|
|
156
|
+
state == States.UnknownStatus
|
|
157
|
+
or state == States.Stopping
|
|
158
|
+
or state == States.BlockedProtectiveStop
|
|
159
|
+
or state == States.Offline
|
|
160
|
+
or state == States.Home
|
|
161
|
+
):
|
|
146
162
|
error_message = (
|
|
147
163
|
f"Conflict - Stop command received in invalid state - State: {state}"
|
|
148
164
|
)
|
isar/config/settings.py
CHANGED
|
@@ -82,6 +82,9 @@ class Settings(BaseSettings):
|
|
|
82
82
|
# FastAPI port
|
|
83
83
|
API_PORT: int = Field(default=3000)
|
|
84
84
|
|
|
85
|
+
# Determines how long delay time should be allowed before returning home
|
|
86
|
+
RETURN_HOME_DELAY: int = Field(default=10)
|
|
87
|
+
|
|
85
88
|
# Determines which mission planner module is used by ISAR
|
|
86
89
|
MISSION_PLANNER: str = Field(default="local")
|
|
87
90
|
|
|
@@ -311,7 +314,7 @@ class RobotSettings(BaseSettings):
|
|
|
311
314
|
|
|
312
315
|
# ISAR steps the robot is capable of performing
|
|
313
316
|
# This should be set in the robot package settings.env file
|
|
314
|
-
CAPABILITIES: List[str] = Field(default=["
|
|
317
|
+
CAPABILITIES: List[str] = Field(default=["take_image"])
|
|
315
318
|
|
|
316
319
|
# Model of the robot which ISAR is connected to
|
|
317
320
|
# This should be set in the robot package settings.env file
|
|
@@ -29,6 +29,7 @@ class APIRequests:
|
|
|
29
29
|
self.stop_mission: QueueIO = QueueIO(input_size=1, output_size=1)
|
|
30
30
|
self.pause_mission: QueueIO = QueueIO(input_size=1, output_size=1)
|
|
31
31
|
self.resume_mission: QueueIO = QueueIO(input_size=1, output_size=1)
|
|
32
|
+
self.return_home: QueueIO = QueueIO(input_size=1, output_size=1)
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
class StateMachineEvents:
|
|
@@ -30,3 +30,9 @@ def check_for_event(queue: Queue[T]) -> Optional[T]:
|
|
|
30
30
|
return queue.get(block=False)
|
|
31
31
|
except Empty:
|
|
32
32
|
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def check_for_event_without_consumption(queue: Queue[T]) -> bool:
|
|
36
|
+
return (
|
|
37
|
+
queue.qsize() != 0
|
|
38
|
+
) # Queue size is not reliable, but should be sufficient for this case
|
isar/modules.py
CHANGED
|
@@ -70,12 +70,19 @@ class ApplicationContainer(containers.DeclarativeContainer):
|
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
# Storage
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
storage_handlers_temp = []
|
|
74
|
+
if settings.STORAGE_LOCAL_ENABLED:
|
|
75
|
+
local_storage = providers.Singleton(LocalStorage)
|
|
76
|
+
storage_handlers_temp.append(local_storage)
|
|
77
|
+
if settings.STORAGE_BLOB_ENABLED:
|
|
78
|
+
blob_storage = providers.Singleton(BlobStorage, keyvault=keyvault)
|
|
79
|
+
storage_handlers_temp.append(blob_storage)
|
|
80
|
+
if settings.STORAGE_SLIMM_ENABLED:
|
|
81
|
+
slimm_storage = providers.Singleton(
|
|
82
|
+
SlimmStorage, request_handler=providers.Singleton(RequestHandler)
|
|
83
|
+
)
|
|
84
|
+
storage_handlers_temp.append(slimm_storage)
|
|
85
|
+
storage_handlers = providers.List(*storage_handlers_temp)
|
|
79
86
|
|
|
80
87
|
# Mqtt client
|
|
81
88
|
mqtt_client = (
|
isar/robot/robot.py
CHANGED
|
@@ -11,11 +11,12 @@ from isar.models.communication.queues.events import (
|
|
|
11
11
|
SharedState,
|
|
12
12
|
StateMachineEvents,
|
|
13
13
|
)
|
|
14
|
-
from isar.models.communication.queues.queue_utils import check_for_event
|
|
14
|
+
from isar.models.communication.queues.queue_utils import check_for_event, trigger_event
|
|
15
15
|
from isar.robot.robot_start_mission import RobotStartMissionThread
|
|
16
16
|
from isar.robot.robot_status import RobotStatusThread
|
|
17
17
|
from isar.robot.robot_stop_mission import RobotStopMissionThread
|
|
18
18
|
from isar.robot.robot_task_status import RobotTaskStatusThread
|
|
19
|
+
from robot_interface.models.exceptions.robot_exceptions import ErrorMessage, ErrorReason
|
|
19
20
|
from robot_interface.robot_interface import RobotInterface
|
|
20
21
|
|
|
21
22
|
|
|
@@ -94,6 +95,14 @@ class Robot(object):
|
|
|
94
95
|
self.stop_mission_thread_thread is not None
|
|
95
96
|
and self.start_mission_thread.is_alive()
|
|
96
97
|
):
|
|
98
|
+
error_description = "Received stop mission event while trying to start a mission. Aborting stop attempt."
|
|
99
|
+
error_message = ErrorMessage(
|
|
100
|
+
error_reason=ErrorReason.RobotStillStartingMissionException,
|
|
101
|
+
error_description=error_description,
|
|
102
|
+
)
|
|
103
|
+
trigger_event(
|
|
104
|
+
self.robot_service_events.mission_failed_to_stop, error_message
|
|
105
|
+
)
|
|
97
106
|
return
|
|
98
107
|
self.stop_mission_thread_thread = RobotStopMissionThread(
|
|
99
108
|
self.robot_service_events, self.robot, self.signal_thread_quitting
|
isar/robot/robot_status.py
CHANGED
|
@@ -5,6 +5,7 @@ from threading import Event, Thread
|
|
|
5
5
|
from isar.config.settings import settings
|
|
6
6
|
from isar.models.communication.queues.events import SharedState
|
|
7
7
|
from isar.models.communication.queues.queue_utils import update_shared_state
|
|
8
|
+
from robot_interface.models.exceptions.robot_exceptions import RobotException
|
|
8
9
|
from robot_interface.robot_interface import RobotInterface
|
|
9
10
|
|
|
10
11
|
|
|
@@ -19,7 +20,9 @@ class RobotStatusThread(Thread):
|
|
|
19
20
|
self.shared_state: SharedState = shared_state
|
|
20
21
|
self.robot: RobotInterface = robot
|
|
21
22
|
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
22
|
-
self.last_robot_status_poll_time: float =
|
|
23
|
+
self.last_robot_status_poll_time: float = (
|
|
24
|
+
time.time() - settings.ROBOT_API_STATUS_POLL_INTERVAL
|
|
25
|
+
)
|
|
23
26
|
Thread.__init__(self, name="Robot status thread")
|
|
24
27
|
|
|
25
28
|
def stop(self) -> None:
|
|
@@ -40,9 +43,12 @@ class RobotStatusThread(Thread):
|
|
|
40
43
|
while not self.signal_thread_quitting.wait(0.001):
|
|
41
44
|
if not self._is_ready_to_poll_for_status():
|
|
42
45
|
continue
|
|
46
|
+
try:
|
|
47
|
+
robot_status = self.robot.robot_status()
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
update_shared_state(self.shared_state.robot_status, robot_status)
|
|
50
|
+
self.last_robot_status_poll_time = time.time()
|
|
51
|
+
except RobotException as e:
|
|
52
|
+
self.logger.error(f"Failed to retrieve robot status: {e}")
|
|
53
|
+
continue
|
|
48
54
|
self.logger.info("Exiting robot status thread")
|
|
@@ -119,19 +119,47 @@ class SchedulingUtilities:
|
|
|
119
119
|
return True
|
|
120
120
|
|
|
121
121
|
def verify_state_machine_ready_to_receive_mission(self, state: States) -> bool:
|
|
122
|
-
"""Verify that the state machine is
|
|
122
|
+
"""Verify that the state machine is ready to receive a mission
|
|
123
123
|
|
|
124
124
|
Raises
|
|
125
125
|
------
|
|
126
126
|
HTTPException 409 Conflict
|
|
127
|
-
If state machine is not
|
|
127
|
+
If state machine is not home, robot standing still, awaiting next mission
|
|
128
|
+
or returning home and therefore cannot start a new mission
|
|
128
129
|
"""
|
|
129
|
-
if
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
if (
|
|
131
|
+
state == States.RobotStandingStill
|
|
132
|
+
or state == States.Home
|
|
133
|
+
or state == States.AwaitNextMission
|
|
134
|
+
or state == States.ReturningHome
|
|
135
|
+
):
|
|
136
|
+
return True
|
|
133
137
|
|
|
134
|
-
|
|
138
|
+
error_message = f"Conflict - Robot is not home, robot standing still, awaiting next mission or returning home - State: {state}"
|
|
139
|
+
self.logger.warning(error_message)
|
|
140
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
141
|
+
|
|
142
|
+
def verify_state_machine_ready_to_receive_return_home_mission(
|
|
143
|
+
self, state: States
|
|
144
|
+
) -> bool:
|
|
145
|
+
"""Verify that the state machine is ready to receive a return home mission
|
|
146
|
+
|
|
147
|
+
Raises
|
|
148
|
+
------
|
|
149
|
+
HTTPException 409 Conflict
|
|
150
|
+
If state machine is not home, robot standing still or awaiting next mission
|
|
151
|
+
and therefore cannot start a new return home mission
|
|
152
|
+
"""
|
|
153
|
+
if (
|
|
154
|
+
state == States.RobotStandingStill
|
|
155
|
+
or state == States.Home
|
|
156
|
+
or state == States.AwaitNextMission
|
|
157
|
+
):
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
error_message = f"Conflict - Robot is not home, robot standing still or awaiting next mission - State: {state}"
|
|
161
|
+
self.logger.warning(error_message)
|
|
162
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
135
163
|
|
|
136
164
|
def start_mission(
|
|
137
165
|
self,
|
|
@@ -157,6 +185,31 @@ class SchedulingUtilities:
|
|
|
157
185
|
)
|
|
158
186
|
self.logger.info("OK - Mission started in ISAR")
|
|
159
187
|
|
|
188
|
+
def return_home(
|
|
189
|
+
self,
|
|
190
|
+
) -> None:
|
|
191
|
+
"""Start return home mission
|
|
192
|
+
|
|
193
|
+
Raises
|
|
194
|
+
------
|
|
195
|
+
HTTTPException 408 Request timeout
|
|
196
|
+
If there is a timeout while communicating with the state machine
|
|
197
|
+
"""
|
|
198
|
+
try:
|
|
199
|
+
self._send_command(
|
|
200
|
+
True,
|
|
201
|
+
self.api_events.return_home,
|
|
202
|
+
)
|
|
203
|
+
except QueueTimeoutError:
|
|
204
|
+
error_message = (
|
|
205
|
+
"Internal Server Error - Failed to start return home mission in ISAR"
|
|
206
|
+
)
|
|
207
|
+
self.logger.error(error_message)
|
|
208
|
+
raise HTTPException(
|
|
209
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
210
|
+
)
|
|
211
|
+
self.logger.info("OK - Return home mission started in ISAR")
|
|
212
|
+
|
|
160
213
|
def pause_mission(self) -> ControlMissionResponse:
|
|
161
214
|
"""Pause mission
|
|
162
215
|
|
|
@@ -17,32 +17,20 @@ from isar.mission_planner.task_selector_interface import (
|
|
|
17
17
|
)
|
|
18
18
|
from isar.models.communication.queues.events import Events, SharedState
|
|
19
19
|
from isar.models.communication.queues.queue_utils import update_shared_state
|
|
20
|
+
from isar.state_machine.states.await_next_mission import AwaitNextMission
|
|
20
21
|
from isar.state_machine.states.blocked_protective_stop import BlockedProtectiveStop
|
|
21
|
-
from isar.state_machine.states.
|
|
22
|
+
from isar.state_machine.states.home import Home
|
|
22
23
|
from isar.state_machine.states.monitor import Monitor
|
|
23
|
-
from isar.state_machine.states.off import Off
|
|
24
24
|
from isar.state_machine.states.offline import Offline
|
|
25
25
|
from isar.state_machine.states.paused import Paused
|
|
26
|
-
from isar.state_machine.states.
|
|
26
|
+
from isar.state_machine.states.returning_home import ReturningHome
|
|
27
|
+
from isar.state_machine.states.robot_standing_still import RobotStandingStill
|
|
28
|
+
from isar.state_machine.states.stopping import Stopping
|
|
29
|
+
from isar.state_machine.states.unknown_status import UnknownStatus
|
|
27
30
|
from isar.state_machine.states_enum import States
|
|
28
|
-
from isar.state_machine.transitions.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
from isar.state_machine.transitions.finish_mission import finish_mission
|
|
32
|
-
from isar.state_machine.transitions.pause import pause_mission
|
|
33
|
-
from isar.state_machine.transitions.resume import resume_mission
|
|
34
|
-
from isar.state_machine.transitions.start_mission import (
|
|
35
|
-
initialize_robot,
|
|
36
|
-
initiate_mission,
|
|
37
|
-
put_start_mission_on_queue,
|
|
38
|
-
set_mission_to_in_progress,
|
|
39
|
-
trigger_start_mission_or_task_event,
|
|
40
|
-
)
|
|
41
|
-
from isar.state_machine.transitions.stop import (
|
|
42
|
-
stop_mission_cleanup,
|
|
43
|
-
trigger_stop_mission_event,
|
|
44
|
-
)
|
|
45
|
-
from isar.state_machine.transitions.utils import def_transition
|
|
31
|
+
from isar.state_machine.transitions.mission import get_mission_transitions
|
|
32
|
+
from isar.state_machine.transitions.return_home import get_return_home_transitions
|
|
33
|
+
from isar.state_machine.transitions.robot_status import get_robot_status_transitions
|
|
46
34
|
from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
|
|
47
35
|
from robot_interface.models.mission.mission import Mission
|
|
48
36
|
from robot_interface.models.mission.status import RobotStatus, TaskStatus
|
|
@@ -101,114 +89,49 @@ class StateMachine(object):
|
|
|
101
89
|
self.signal_state_machine_to_stop: Event = Event()
|
|
102
90
|
|
|
103
91
|
# List of states
|
|
104
|
-
|
|
105
|
-
self.paused_state: State = Paused(self)
|
|
106
|
-
self.idle_state: State = Idle(self)
|
|
92
|
+
# States running mission
|
|
107
93
|
self.monitor_state: State = Monitor(self)
|
|
108
|
-
self.
|
|
94
|
+
self.returning_home_state: State = ReturningHome(self)
|
|
95
|
+
self.stopping_state: State = Stopping(self)
|
|
96
|
+
self.paused_state: State = Paused(self)
|
|
97
|
+
|
|
98
|
+
# States Waiting for mission
|
|
99
|
+
self.await_next_mission_state: State = AwaitNextMission(self)
|
|
100
|
+
self.home_state: State = Home(self)
|
|
101
|
+
self.robot_standing_still_state: State = RobotStandingStill(self)
|
|
102
|
+
|
|
103
|
+
# Status states
|
|
109
104
|
self.offline_state: State = Offline(self)
|
|
110
|
-
self.
|
|
105
|
+
self.blocked_protective_stopping_state: State = BlockedProtectiveStop(self)
|
|
106
|
+
|
|
107
|
+
# Error and special status states
|
|
108
|
+
self.unknown_status_state: State = UnknownStatus(self)
|
|
111
109
|
|
|
112
110
|
self.states: List[State] = [
|
|
113
|
-
self.off_state,
|
|
114
|
-
self.idle_state,
|
|
115
111
|
self.monitor_state,
|
|
116
|
-
self.
|
|
112
|
+
self.returning_home_state,
|
|
113
|
+
self.stopping_state,
|
|
117
114
|
self.paused_state,
|
|
115
|
+
self.await_next_mission_state,
|
|
116
|
+
self.home_state,
|
|
117
|
+
self.robot_standing_still_state,
|
|
118
118
|
self.offline_state,
|
|
119
|
-
self.
|
|
119
|
+
self.blocked_protective_stopping_state,
|
|
120
|
+
self.unknown_status_state,
|
|
120
121
|
]
|
|
121
122
|
|
|
122
|
-
self.machine = Machine(
|
|
123
|
-
|
|
124
|
-
[
|
|
125
|
-
{
|
|
126
|
-
"trigger": "start_machine",
|
|
127
|
-
"source": self.off_state,
|
|
128
|
-
"dest": self.idle_state,
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
"trigger": "pause",
|
|
132
|
-
"source": self.monitor_state,
|
|
133
|
-
"dest": self.paused_state,
|
|
134
|
-
"before": def_transition(self, pause_mission),
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
"trigger": "stop",
|
|
138
|
-
"source": [
|
|
139
|
-
self.idle_state,
|
|
140
|
-
self.monitor_state,
|
|
141
|
-
self.paused_state,
|
|
142
|
-
],
|
|
143
|
-
"dest": self.stop_state,
|
|
144
|
-
"before": def_transition(self, trigger_stop_mission_event),
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
"trigger": "request_mission_start",
|
|
148
|
-
"source": self.idle_state,
|
|
149
|
-
"dest": self.monitor_state,
|
|
150
|
-
"prepare": def_transition(self, put_start_mission_on_queue),
|
|
151
|
-
"conditions": [
|
|
152
|
-
def_transition(self, initiate_mission),
|
|
153
|
-
def_transition(self, initialize_robot),
|
|
154
|
-
],
|
|
155
|
-
"before": [
|
|
156
|
-
def_transition(self, set_mission_to_in_progress),
|
|
157
|
-
def_transition(self, trigger_start_mission_or_task_event),
|
|
158
|
-
],
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
"trigger": "request_mission_start",
|
|
162
|
-
"source": self.idle_state,
|
|
163
|
-
"dest": self.idle_state,
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
"trigger": "mission_failed_to_start",
|
|
167
|
-
"source": self.monitor_state,
|
|
168
|
-
"dest": self.idle_state,
|
|
169
|
-
"before": def_transition(self, report_failed_mission_and_finalize),
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
"trigger": "resume",
|
|
173
|
-
"source": self.paused_state,
|
|
174
|
-
"dest": self.monitor_state,
|
|
175
|
-
"before": def_transition(self, resume_mission),
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
"trigger": "mission_finished",
|
|
179
|
-
"source": self.monitor_state,
|
|
180
|
-
"dest": self.idle_state,
|
|
181
|
-
"before": def_transition(self, finish_mission),
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
"trigger": "mission_stopped",
|
|
185
|
-
"source": self.stop_state,
|
|
186
|
-
"dest": self.idle_state,
|
|
187
|
-
"before": def_transition(self, stop_mission_cleanup),
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
"trigger": "robot_turned_offline",
|
|
191
|
-
"source": [self.idle_state, self.blocked_protective_stop],
|
|
192
|
-
"dest": self.offline_state,
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
"trigger": "robot_turned_online",
|
|
196
|
-
"source": self.offline_state,
|
|
197
|
-
"dest": self.idle_state,
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
"trigger": "robot_protective_stop_engaged",
|
|
201
|
-
"source": [self.idle_state, self.offline_state],
|
|
202
|
-
"dest": self.blocked_protective_stop,
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
"trigger": "robot_protective_stop_disengaged",
|
|
206
|
-
"source": self.blocked_protective_stop,
|
|
207
|
-
"dest": self.idle_state,
|
|
208
|
-
},
|
|
209
|
-
]
|
|
123
|
+
self.machine = Machine(
|
|
124
|
+
self, states=self.states, initial="unknown_status", queued=True
|
|
210
125
|
)
|
|
211
126
|
|
|
127
|
+
self.transitions: List[dict] = []
|
|
128
|
+
|
|
129
|
+
self.transitions.extend(get_mission_transitions(self))
|
|
130
|
+
self.transitions.extend(get_return_home_transitions(self))
|
|
131
|
+
self.transitions.extend(get_robot_status_transitions(self))
|
|
132
|
+
|
|
133
|
+
self.machine.add_transitions(self.transitions)
|
|
134
|
+
|
|
212
135
|
self.stop_robot_attempts_limit: int = stop_robot_attempts_limit
|
|
213
136
|
self.sleep_time: float = sleep_time
|
|
214
137
|
|
|
@@ -237,8 +160,8 @@ class StateMachine(object):
|
|
|
237
160
|
self.reset_state_machine()
|
|
238
161
|
|
|
239
162
|
def begin(self):
|
|
240
|
-
"""Starts the state machine. Transitions into
|
|
241
|
-
self.
|
|
163
|
+
"""Starts the state machine. Transitions into unknown status state."""
|
|
164
|
+
self.robot_status_changed() # type: ignore
|
|
242
165
|
|
|
243
166
|
def terminate(self):
|
|
244
167
|
self.logger.info("Stopping state machine")
|
|
@@ -358,8 +281,15 @@ class StateMachine(object):
|
|
|
358
281
|
)
|
|
359
282
|
|
|
360
283
|
def _current_status(self) -> RobotStatus:
|
|
361
|
-
if
|
|
284
|
+
if (
|
|
285
|
+
self.current_state == States.RobotStandingStill
|
|
286
|
+
or self.current_state == States.AwaitNextMission
|
|
287
|
+
):
|
|
362
288
|
return RobotStatus.Available
|
|
289
|
+
elif self.current_state == States.Home:
|
|
290
|
+
return RobotStatus.Home
|
|
291
|
+
elif self.current_state == States.ReturningHome:
|
|
292
|
+
return RobotStatus.ReturningHome
|
|
363
293
|
elif self.current_state == States.Offline:
|
|
364
294
|
return RobotStatus.Offline
|
|
365
295
|
elif self.current_state == States.BlockedProtectiveStop:
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from queue import Queue
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
from transitions import State
|
|
7
|
+
|
|
8
|
+
from isar.config.settings import settings
|
|
9
|
+
from isar.models.communication.message import StartMissionMessage
|
|
10
|
+
from isar.models.communication.queues.queue_io import QueueIO
|
|
11
|
+
from isar.models.communication.queues.queue_utils import check_for_event
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from isar.state_machine.state_machine import StateMachine
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AwaitNextMission(State):
|
|
18
|
+
def __init__(self, state_machine: "StateMachine") -> None:
|
|
19
|
+
super().__init__(
|
|
20
|
+
name="await_next_mission", on_enter=self.start, on_exit=self.stop
|
|
21
|
+
)
|
|
22
|
+
self.state_machine: "StateMachine" = state_machine
|
|
23
|
+
self.logger = logging.getLogger("state_machine")
|
|
24
|
+
self.entered_time: float = time.time()
|
|
25
|
+
self.return_home_delay: float = settings.RETURN_HOME_DELAY
|
|
26
|
+
self.events = self.state_machine.events
|
|
27
|
+
self.shared_state = self.state_machine.shared_state
|
|
28
|
+
self.signal_state_machine_to_stop = state_machine.signal_state_machine_to_stop
|
|
29
|
+
|
|
30
|
+
def start(self) -> None:
|
|
31
|
+
self.state_machine.update_state()
|
|
32
|
+
self.entered_time = time.time()
|
|
33
|
+
self._run()
|
|
34
|
+
|
|
35
|
+
def stop(self) -> None:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def _check_and_handle_stop_mission_event(self, event: Queue) -> bool:
|
|
39
|
+
if check_for_event(event):
|
|
40
|
+
self.state_machine.stop() # type: ignore
|
|
41
|
+
return True
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
def _check_and_handle_start_mission_event(
|
|
45
|
+
self, event: Queue[StartMissionMessage]
|
|
46
|
+
) -> bool:
|
|
47
|
+
start_mission: Optional[StartMissionMessage] = check_for_event(event)
|
|
48
|
+
if start_mission:
|
|
49
|
+
self.state_machine.start_mission(mission=start_mission.mission)
|
|
50
|
+
self.state_machine.request_mission_start() # type: ignore
|
|
51
|
+
return True
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
def _check_and_handle_return_home_event(self, event: QueueIO) -> bool:
|
|
55
|
+
if check_for_event(event.input):
|
|
56
|
+
event.output.put(True)
|
|
57
|
+
self.state_machine.request_return_home() # type: ignore
|
|
58
|
+
return True
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def _should_return_home(self) -> bool:
|
|
62
|
+
time_since_entered = time.time() - self.entered_time
|
|
63
|
+
return time_since_entered > self.return_home_delay
|
|
64
|
+
|
|
65
|
+
def _run(self) -> None:
|
|
66
|
+
while True:
|
|
67
|
+
if self.signal_state_machine_to_stop.is_set():
|
|
68
|
+
self.logger.info(
|
|
69
|
+
"Stopping state machine from %s state", self.__class__.__name__
|
|
70
|
+
)
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
if self._check_and_handle_stop_mission_event(
|
|
74
|
+
self.events.api_requests.stop_mission.input
|
|
75
|
+
):
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
if self._check_and_handle_start_mission_event(
|
|
79
|
+
self.events.api_requests.start_mission.input
|
|
80
|
+
):
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
if self._check_and_handle_return_home_event(
|
|
84
|
+
self.events.api_requests.return_home
|
|
85
|
+
):
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
if self._should_return_home():
|
|
89
|
+
self.state_machine.request_return_home() # type: ignore
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
time.sleep(self.state_machine.sleep_time)
|
|
@@ -41,11 +41,8 @@ class BlockedProtectiveStop(State):
|
|
|
41
41
|
robot_status: RobotStatus = check_shared_state(
|
|
42
42
|
self.shared_state.robot_status
|
|
43
43
|
)
|
|
44
|
-
if robot_status
|
|
45
|
-
transition = self.state_machine.
|
|
46
|
-
break
|
|
47
|
-
elif robot_status != RobotStatus.BlockedProtectiveStop:
|
|
48
|
-
transition = self.state_machine.robot_protective_stop_disengaged # type: ignore
|
|
44
|
+
if robot_status != RobotStatus.BlockedProtectiveStop:
|
|
45
|
+
transition = self.state_machine.robot_status_changed # type: ignore
|
|
49
46
|
break
|
|
50
47
|
|
|
51
48
|
time.sleep(self.state_machine.sleep_time)
|