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.
Files changed (124) hide show
  1. isar/apis/api.py +148 -76
  2. isar/apis/models/__init__.py +0 -1
  3. isar/apis/models/models.py +21 -11
  4. isar/apis/models/start_mission_definition.py +110 -168
  5. isar/apis/robot_control/robot_controller.py +41 -0
  6. isar/apis/schedule/scheduling_controller.py +124 -162
  7. isar/apis/security/authentication.py +5 -5
  8. isar/config/certs/ca-cert.pem +33 -31
  9. isar/config/keyvault/keyvault_service.py +1 -1
  10. isar/config/log.py +45 -40
  11. isar/config/logging.conf +16 -31
  12. isar/config/open_telemetry.py +102 -0
  13. isar/config/predefined_mission_definition/default_exr.json +0 -2
  14. isar/config/predefined_mission_definition/default_mission.json +1 -5
  15. isar/config/predefined_mission_definition/default_turtlebot.json +4 -11
  16. isar/config/predefined_missions/default.json +67 -87
  17. isar/config/predefined_missions/default_extra_capabilities.json +107 -0
  18. isar/config/settings.py +76 -111
  19. isar/eventhandlers/eventhandler.py +123 -0
  20. isar/mission_planner/local_planner.py +6 -20
  21. isar/mission_planner/mission_planner_interface.py +1 -1
  22. isar/models/events.py +184 -0
  23. isar/models/status.py +18 -0
  24. isar/modules.py +118 -199
  25. isar/robot/robot.py +377 -0
  26. isar/robot/robot_battery.py +60 -0
  27. isar/robot/robot_monitor_mission.py +357 -0
  28. isar/robot/robot_pause_mission.py +74 -0
  29. isar/robot/robot_resume_mission.py +67 -0
  30. isar/robot/robot_start_mission.py +66 -0
  31. isar/robot/robot_status.py +61 -0
  32. isar/robot/robot_stop_mission.py +68 -0
  33. isar/robot/robot_upload_inspection.py +75 -0
  34. isar/script.py +57 -40
  35. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  36. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
  37. isar/services/service_connections/mqtt/robot_info_publisher.py +3 -3
  38. isar/services/service_connections/persistent_memory.py +69 -0
  39. isar/services/utilities/mqtt_utilities.py +93 -0
  40. isar/services/utilities/robot_utilities.py +20 -0
  41. isar/services/utilities/scheduling_utilities.py +393 -65
  42. isar/state_machine/state_machine.py +219 -538
  43. isar/state_machine/states/__init__.py +0 -8
  44. isar/state_machine/states/await_next_mission.py +114 -0
  45. isar/state_machine/states/blocked_protective_stop.py +60 -0
  46. isar/state_machine/states/going_to_lockdown.py +95 -0
  47. isar/state_machine/states/going_to_recharging.py +92 -0
  48. isar/state_machine/states/home.py +115 -0
  49. isar/state_machine/states/intervention_needed.py +77 -0
  50. isar/state_machine/states/lockdown.py +38 -0
  51. isar/state_machine/states/maintenance.py +36 -0
  52. isar/state_machine/states/monitor.py +137 -247
  53. isar/state_machine/states/offline.py +51 -53
  54. isar/state_machine/states/paused.py +92 -23
  55. isar/state_machine/states/pausing.py +48 -0
  56. isar/state_machine/states/pausing_return_home.py +48 -0
  57. isar/state_machine/states/recharging.py +80 -0
  58. isar/state_machine/states/resuming.py +57 -0
  59. isar/state_machine/states/resuming_return_home.py +64 -0
  60. isar/state_machine/states/return_home_paused.py +109 -0
  61. isar/state_machine/states/returning_home.py +217 -0
  62. isar/state_machine/states/stopping.py +61 -0
  63. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  64. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  65. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  66. isar/state_machine/states/stopping_return_home.py +77 -0
  67. isar/state_machine/states/unknown_status.py +72 -0
  68. isar/state_machine/states_enum.py +21 -5
  69. isar/state_machine/transitions/mission.py +192 -0
  70. isar/state_machine/transitions/return_home.py +106 -0
  71. isar/state_machine/transitions/robot_status.py +80 -0
  72. isar/state_machine/utils/common_event_handlers.py +73 -0
  73. isar/storage/blob_storage.py +70 -52
  74. isar/storage/local_storage.py +25 -12
  75. isar/storage/storage_interface.py +28 -7
  76. isar/storage/uploader.py +174 -55
  77. isar/storage/utilities.py +32 -29
  78. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/METADATA +73 -110
  79. isar-1.34.9.dist-info/RECORD +135 -0
  80. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
  81. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/entry_points.txt +1 -0
  82. robot_interface/models/exceptions/robot_exceptions.py +91 -41
  83. robot_interface/models/initialize/__init__.py +0 -1
  84. robot_interface/models/inspection/__init__.py +0 -13
  85. robot_interface/models/inspection/inspection.py +42 -33
  86. robot_interface/models/mission/mission.py +14 -15
  87. robot_interface/models/mission/status.py +20 -26
  88. robot_interface/models/mission/task.py +154 -121
  89. robot_interface/models/robots/battery_state.py +6 -0
  90. robot_interface/models/robots/media.py +13 -0
  91. robot_interface/models/robots/robot_model.py +7 -7
  92. robot_interface/robot_interface.py +119 -84
  93. robot_interface/telemetry/mqtt_client.py +74 -12
  94. robot_interface/telemetry/payloads.py +91 -13
  95. robot_interface/utilities/json_service.py +7 -1
  96. isar/config/predefined_missions/default_turtlebot.json +0 -110
  97. isar/config/predefined_poses/__init__.py +0 -0
  98. isar/config/predefined_poses/predefined_poses.py +0 -616
  99. isar/config/settings.env +0 -25
  100. isar/mission_planner/sequential_task_selector.py +0 -23
  101. isar/mission_planner/task_selector_interface.py +0 -31
  102. isar/models/communication/__init__.py +0 -0
  103. isar/models/communication/message.py +0 -12
  104. isar/models/communication/queues/__init__.py +0 -4
  105. isar/models/communication/queues/queue_io.py +0 -12
  106. isar/models/communication/queues/queue_timeout_error.py +0 -2
  107. isar/models/communication/queues/queues.py +0 -19
  108. isar/models/communication/queues/status_queue.py +0 -20
  109. isar/models/mission_metadata/__init__.py +0 -0
  110. isar/services/readers/__init__.py +0 -0
  111. isar/services/readers/base_reader.py +0 -37
  112. isar/services/service_connections/stid/__init__.py +0 -0
  113. isar/services/utilities/queue_utilities.py +0 -39
  114. isar/state_machine/states/idle.py +0 -85
  115. isar/state_machine/states/initialize.py +0 -71
  116. isar/state_machine/states/initiate.py +0 -142
  117. isar/state_machine/states/off.py +0 -18
  118. isar/state_machine/states/stop.py +0 -95
  119. isar/storage/slimm_storage.py +0 -191
  120. isar-1.20.2.dist-info/RECORD +0 -116
  121. robot_interface/models/initialize/initialize_params.py +0 -9
  122. robot_interface/models/mission/step.py +0 -234
  123. {isar-1.20.2.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
  124. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
@@ -1,12 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Optional
3
-
4
- from alitra import Pose
5
-
6
- from robot_interface.models.mission.mission import Mission
7
-
8
-
9
- @dataclass
10
- class StartMissionMessage:
11
- mission: Mission
12
- initial_pose: Optional[Pose]
@@ -1,4 +0,0 @@
1
- from .queue_io import QueueIO
2
- from .queue_timeout_error import QueueTimeoutError
3
- from .queues import Queues
4
- from .status_queue import StatusQueue
@@ -1,12 +0,0 @@
1
- from queue import Queue
2
-
3
-
4
- class QueueIO:
5
- """
6
- Creates input and output queue. The queues are defined such that the input is from
7
- api to state machine while the output is from state machine to api.
8
- """
9
-
10
- def __init__(self, input_size: int = 0, output_size: int = 0):
11
- self.input: Queue = Queue(maxsize=input_size)
12
- self.output: Queue = Queue(maxsize=output_size)
@@ -1,2 +0,0 @@
1
- class QueueTimeoutError(Exception):
2
- pass
@@ -1,19 +0,0 @@
1
- from queue import Queue
2
-
3
- from isar.config.settings import settings
4
- from isar.models.communication.queues.queue_io import QueueIO
5
- from isar.models.communication.queues.status_queue import StatusQueue
6
-
7
-
8
- class Queues:
9
- def __init__(self) -> None:
10
- self.start_mission: QueueIO = QueueIO(input_size=1, output_size=1)
11
- self.stop_mission: QueueIO = QueueIO(input_size=1, output_size=1)
12
- self.pause_mission: QueueIO = QueueIO(input_size=1, output_size=1)
13
- self.resume_mission: QueueIO = QueueIO(input_size=1, output_size=1)
14
- self.single_action: QueueIO = QueueIO(input_size=1, output_size=1)
15
- self.upload_queue: Queue = Queue(maxsize=10)
16
- self.state: StatusQueue = StatusQueue()
17
-
18
- if settings.MQTT_ENABLED:
19
- self.mqtt_queue: Queue = Queue()
@@ -1,20 +0,0 @@
1
- from collections import deque
2
- from queue import Empty, Queue
3
- from typing import Any
4
-
5
-
6
- class StatusQueue(Queue):
7
- def __init__(self) -> None:
8
- super().__init__()
9
-
10
- def check(self) -> Any:
11
- if not self._qsize():
12
- raise Empty
13
- with self.mutex:
14
- l = list(self.queue)
15
- return l.pop()
16
-
17
- def update(self, item: Any):
18
- with self.mutex:
19
- self.queue = deque()
20
- self.queue.append(item)
File without changes
File without changes
@@ -1,37 +0,0 @@
1
- import json
2
- import logging
3
- from dataclasses import is_dataclass
4
- from logging import Logger
5
- from pathlib import Path
6
- from typing import Any, Optional
7
-
8
- from dacite import Config, from_dict
9
-
10
- logger: Logger = logging.getLogger("api")
11
-
12
-
13
- class BaseReader:
14
- @staticmethod
15
- def read_json(location: Path) -> dict:
16
- with open(location) as json_file:
17
- return json.load(json_file)
18
-
19
- @staticmethod
20
- def dict_to_dataclass(
21
- dataclass_dict: dict,
22
- target_dataclass: Any,
23
- cast_config: list = [],
24
- strict_config: bool = False,
25
- ) -> Optional[Any]:
26
- if not is_dataclass(target_dataclass):
27
- raise BaseReaderError("{target_dataclass} is not a dataclass")
28
- generated_dataclass = from_dict(
29
- data_class=target_dataclass,
30
- data=dataclass_dict,
31
- config=Config(cast=cast_config, strict=strict_config),
32
- )
33
- return generated_dataclass
34
-
35
-
36
- class BaseReaderError(Exception):
37
- pass
File without changes
@@ -1,39 +0,0 @@
1
- import logging
2
- from queue import Empty, Queue
3
- from typing import Any
4
-
5
- from isar.models.communication.queues.queue_timeout_error import QueueTimeoutError
6
-
7
- logger = logging.getLogger("api")
8
-
9
-
10
- class QueueUtilities:
11
- """
12
- Contains utility functions for handling queue communication between threads.
13
- """
14
-
15
- @staticmethod
16
- def check_queue(queue: Queue, queue_timeout: int = None) -> Any:
17
- """
18
- Checks if there is a message on a queue. If a timeout is specified the function
19
- will raise a QueueTimeoutError if there is no message within the timeout. If
20
- there is no timeout specified this function will block.
21
- :param queue: The queue to be checked for a message
22
- :param queue_timeout: Timeout in seconds
23
- :return: Message found on queue
24
- :raises QueueTimeoutError
25
- """
26
- try:
27
- message: Any = queue.get(timeout=queue_timeout)
28
- except Empty:
29
- logger.error("Queue timed out")
30
- raise QueueTimeoutError
31
- return message
32
-
33
- @staticmethod
34
- def clear_queue(queue: Queue) -> None:
35
- while True:
36
- try:
37
- queue.get(block=False)
38
- except Empty:
39
- break
@@ -1,85 +0,0 @@
1
- import logging
2
- import time
3
- from typing import TYPE_CHECKING, Optional
4
-
5
- from transitions import State
6
-
7
- from isar.config.settings import settings
8
- from isar.models.communication.message import StartMissionMessage
9
- from isar.services.utilities.threaded_request import (
10
- ThreadedRequest,
11
- ThreadedRequestNotFinishedError,
12
- )
13
- from robot_interface.models.exceptions.robot_exceptions import RobotException
14
- from robot_interface.models.mission.status import RobotStatus
15
-
16
- if TYPE_CHECKING:
17
- from isar.state_machine.state_machine import StateMachine
18
-
19
-
20
- class Idle(State):
21
- def __init__(self, state_machine: "StateMachine") -> None:
22
- super().__init__(name="idle", on_enter=self.start, on_exit=self.stop)
23
- self.state_machine: "StateMachine" = state_machine
24
- self.logger = logging.getLogger("state_machine")
25
- self.robot_status_thread: Optional[ThreadedRequest] = None
26
- self.last_robot_status_poll_time: float = time.time()
27
-
28
- def start(self) -> None:
29
- self.state_machine.update_state()
30
- self._run()
31
-
32
- def stop(self) -> None:
33
- if self.robot_status_thread:
34
- self.robot_status_thread.wait_for_thread()
35
- self.robot_status_thread = None
36
-
37
- def _run(self) -> None:
38
- while True:
39
- start_mission: Optional[StartMissionMessage] = (
40
- self.state_machine.should_start_mission()
41
- )
42
- if start_mission:
43
- self.state_machine.start_mission(
44
- mission=start_mission.mission,
45
- initial_pose=start_mission.initial_pose,
46
- )
47
- transition = self.state_machine.mission_started # type: ignore
48
- break
49
- time.sleep(self.state_machine.sleep_time)
50
-
51
- time_from_last_robot_status_poll = (
52
- time.time() - self.last_robot_status_poll_time
53
- )
54
- if (
55
- time_from_last_robot_status_poll
56
- < settings.ROBOT_API_STATUS_POLL_INTERVAL
57
- ):
58
- continue
59
-
60
- if not self.robot_status_thread:
61
- self.robot_status_thread = ThreadedRequest(
62
- request_func=self.state_machine.robot.robot_status
63
- )
64
- self.robot_status_thread.start_thread(
65
- name="State Machine Offline Get Robot Status"
66
- )
67
-
68
- try:
69
- robot_status: RobotStatus = self.robot_status_thread.get_output()
70
- except ThreadedRequestNotFinishedError:
71
- time.sleep(self.state_machine.sleep_time)
72
- continue
73
-
74
- except (RobotException,) as e:
75
- self.logger.error(
76
- f"Failed to get robot status because: {e.error_description}"
77
- )
78
-
79
- self.last_robot_status_poll_time = time.time()
80
-
81
- if robot_status == RobotStatus.Offline:
82
- transition = self.state_machine.robot_turned_offline # type: ignore
83
- break
84
-
85
- transition()
@@ -1,71 +0,0 @@
1
- import logging
2
- import time
3
- from typing import TYPE_CHECKING, Callable, Optional
4
-
5
- from injector import inject
6
- from transitions import State
7
-
8
- from isar.services.utilities.threaded_request import (
9
- ThreadedRequest,
10
- ThreadedRequestNotFinishedError,
11
- )
12
- from robot_interface.models.exceptions.robot_exceptions import (
13
- ErrorMessage,
14
- RobotException,
15
- RobotInitializeException,
16
- )
17
-
18
- if TYPE_CHECKING:
19
- from isar.state_machine.state_machine import StateMachine
20
-
21
-
22
- class Initialize(State):
23
- @inject
24
- def __init__(self, state_machine: "StateMachine") -> None:
25
- super().__init__(name="initialize", on_enter=self.start, on_exit=self.stop)
26
- self.state_machine: "StateMachine" = state_machine
27
-
28
- self.logger = logging.getLogger("state_machine")
29
- self.initialize_thread: Optional[ThreadedRequest] = None
30
-
31
- def start(self) -> None:
32
- self.state_machine.update_state()
33
- self._run()
34
-
35
- def stop(self) -> None:
36
- if self.initialize_thread:
37
- self.initialize_thread.wait_for_thread()
38
- self.initialize_thread = None
39
-
40
- def _run(self) -> None:
41
- transition: Callable
42
- while True:
43
- if not self.initialize_thread:
44
- self.initialize_thread = ThreadedRequest(
45
- self.state_machine.robot.initialize
46
- )
47
- self.initialize_thread.start_thread(
48
- self.state_machine.get_initialize_params(),
49
- name="State Machine Initialize Robot",
50
- )
51
-
52
- try:
53
- self.initialize_thread.get_output()
54
-
55
- except ThreadedRequestNotFinishedError:
56
- time.sleep(self.state_machine.sleep_time)
57
- continue
58
-
59
- except (RobotInitializeException, RobotException) as e:
60
- self.state_machine.current_step.error_message = ErrorMessage(
61
- error_reason=e.error_reason, error_description=e.error_description
62
- )
63
- self.logger.error(
64
- f"Failed to initialize robot because: {e.error_description}"
65
- )
66
- transition = self.state_machine.initialization_failed # type: ignore
67
- break
68
-
69
- transition = self.state_machine.initialization_successful # type: ignore
70
- break
71
- transition()
@@ -1,142 +0,0 @@
1
- import logging
2
- import time
3
- from typing import TYPE_CHECKING, Any, Callable, Optional
4
-
5
- from transitions import State
6
-
7
- from isar.config.settings import settings
8
- from isar.services.utilities.threaded_request import (
9
- ThreadedRequest,
10
- ThreadedRequestNotFinishedError,
11
- )
12
- from robot_interface.models.exceptions.robot_exceptions import (
13
- ErrorMessage,
14
- RobotException,
15
- RobotInfeasibleMissionException,
16
- RobotInfeasibleStepException,
17
- )
18
-
19
- if TYPE_CHECKING:
20
- from isar.state_machine.state_machine import StateMachine
21
-
22
-
23
- class Initiate(State):
24
- def __init__(self, state_machine: "StateMachine") -> None:
25
- super().__init__(name="initiate", on_enter=self.start, on_exit=self.stop)
26
- self.state_machine: "StateMachine" = state_machine
27
- self.initiate_failure_counter: int = 0
28
- self.initiate_failure_counter_limit: int = (
29
- settings.INITIATE_FAILURE_COUNTER_LIMIT
30
- )
31
- self.logger = logging.getLogger("state_machine")
32
-
33
- self.initiate_thread: Optional[ThreadedRequest] = None
34
-
35
- def start(self) -> None:
36
- self.state_machine.update_state()
37
- self._run()
38
-
39
- def stop(self) -> None:
40
- self.initiate_failure_counter = 0
41
- if self.initiate_thread:
42
- self.initiate_thread.wait_for_thread()
43
- self.initiate_thread = None
44
-
45
- def _run(self) -> None:
46
- transition: Callable
47
- while True:
48
- if self.state_machine.should_stop_mission():
49
- transition = self.state_machine.stop # type: ignore
50
- break
51
-
52
- if self.state_machine.should_pause_mission():
53
- transition = self.state_machine.pause # type: ignore
54
- break
55
-
56
- if not self.state_machine.current_task:
57
- self.logger.info(
58
- f"Completed mission: {self.state_machine.current_mission.id}"
59
- )
60
- transition = self.state_machine.mission_finished # type: ignore
61
- break
62
-
63
- if not self.initiate_thread:
64
- if self.state_machine.stepwise_mission:
65
- self._run_initiate_thread(
66
- initiate_function=self.state_machine.robot.initiate_step,
67
- function_argument=self.state_machine.current_step,
68
- thread_name="State Machine Initiate Step",
69
- )
70
- else:
71
- self._run_initiate_thread(
72
- initiate_function=self.state_machine.robot.initiate_mission,
73
- function_argument=self.state_machine.current_mission,
74
- thread_name="State Machine Initiate Mission",
75
- )
76
-
77
- try:
78
- self.initiate_thread.get_output()
79
- transition = self.state_machine.initiated # type: ignore
80
- break
81
- except ThreadedRequestNotFinishedError:
82
- time.sleep(self.state_machine.sleep_time)
83
- continue
84
- except RobotInfeasibleStepException as e:
85
- self.state_machine.current_step.error_message = ErrorMessage(
86
- error_reason=e.error_reason, error_description=e.error_description
87
- )
88
- self.logger.warning(
89
- f"Failed to initiate step "
90
- f"{str(self.state_machine.current_step.id)[:8]} after retrying "
91
- f"{self.initiate_failure_counter} times because: "
92
- f"{e.error_description}"
93
- )
94
- transition = self.state_machine.initiate_infeasible # type: ignore
95
- break
96
-
97
- except RobotInfeasibleMissionException as e:
98
- self.state_machine.current_mission.error_message = ErrorMessage(
99
- error_reason=e.error_reason, error_description=e.error_description
100
- )
101
- self.logger.warning(
102
- f"Failed to initiate mission "
103
- f"{str(self.state_machine.current_mission.id)[:8]} because: "
104
- f"{e.error_description}"
105
- )
106
- transition = self.state_machine.initiate_failed # type: ignore
107
- break
108
-
109
- except RobotException as e:
110
- self.initiate_thread = None
111
- self.initiate_failure_counter += 1
112
- self.logger.warning(
113
- f"Initiating failed #: {str(self.initiate_failure_counter)} "
114
- f"because: {e.error_description}"
115
- )
116
-
117
- if self.initiate_failure_counter >= self.initiate_failure_counter_limit:
118
- self.state_machine.current_step.error_message = ErrorMessage(
119
- error_reason=e.error_reason,
120
- error_description=e.error_description,
121
- )
122
- self.logger.error(
123
- f"Mission will be cancelled after failing to initiate "
124
- f"{self.initiate_failure_counter_limit} times because: "
125
- f"{e.error_description}"
126
- )
127
- transition = self.state_machine.initiate_failed # type: ignore
128
- break
129
-
130
- time.sleep(self.state_machine.sleep_time)
131
-
132
- transition()
133
-
134
- def _run_initiate_thread(
135
- self, initiate_function: Callable, function_argument: Any, thread_name: str
136
- ) -> None:
137
- self.initiate_thread = ThreadedRequest(request_func=initiate_function)
138
-
139
- self.initiate_thread.start_thread(
140
- function_argument,
141
- name=thread_name,
142
- )
@@ -1,18 +0,0 @@
1
- import logging
2
- from typing import TYPE_CHECKING
3
-
4
- from transitions import State
5
-
6
- if TYPE_CHECKING:
7
- from isar.state_machine.state_machine import StateMachine
8
-
9
-
10
- class Off(State):
11
- def __init__(self, state_machine: "StateMachine"):
12
- super().__init__(name="off", on_enter=self.start)
13
- self.logger = logging.getLogger("state_machine")
14
- self.state_machine: "StateMachine" = state_machine
15
-
16
- def start(self):
17
- self.state_machine.update_state()
18
- self.logger.info(f"State: {self.state_machine.current_state}")
@@ -1,95 +0,0 @@
1
- import logging
2
- import time
3
- from typing import TYPE_CHECKING, Callable, Optional
4
-
5
- from transitions import State
6
-
7
- from isar.services.utilities.threaded_request import (
8
- ThreadedRequest,
9
- ThreadedRequestNotFinishedError,
10
- )
11
- from robot_interface.models.exceptions.robot_exceptions import (
12
- ErrorMessage,
13
- RobotActionException,
14
- RobotException,
15
- )
16
-
17
- if TYPE_CHECKING:
18
- from isar.state_machine.state_machine import StateMachine
19
-
20
-
21
- class Stop(State):
22
- def __init__(self, state_machine: "StateMachine") -> None:
23
- super().__init__(name="stop", on_enter=self.start, on_exit=self.stop)
24
- self.state_machine: "StateMachine" = state_machine
25
- self.logger = logging.getLogger("state_machine")
26
- self.stop_thread: Optional[ThreadedRequest] = None
27
- self._count_number_retries: int = 0
28
-
29
- def start(self) -> None:
30
- self.state_machine.update_state()
31
- self._run()
32
-
33
- def stop(self) -> None:
34
- if self.stop_thread:
35
- self.stop_thread.wait_for_thread()
36
- self.stop_thread = None
37
- self._count_number_retries = 0
38
-
39
- def _run(self) -> None:
40
- transition: Callable
41
- while True:
42
- if not self.stop_thread:
43
- self.stop_thread = ThreadedRequest(self.state_machine.robot.stop)
44
- self.stop_thread.start_thread(name="State Machine Stop Robot")
45
-
46
- if self.state_machine.should_stop_mission():
47
- self.state_machine.stopped = True
48
-
49
- try:
50
- self.stop_thread.get_output()
51
- except ThreadedRequestNotFinishedError:
52
- time.sleep(self.state_machine.sleep_time)
53
- continue
54
-
55
- except (RobotActionException, RobotException) as e:
56
- if self.handle_stop_fail(
57
- retry_limit=self.state_machine.stop_robot_attempts_limit,
58
- error_message=ErrorMessage(
59
- error_reason=e.error_reason,
60
- error_description=e.error_description,
61
- ),
62
- ):
63
- transition = self.state_machine.mission_stopped # type: ignore
64
- break
65
-
66
- self.logger.warning(
67
- f"\nFailed to stop robot because: {e.error_description}"
68
- f"\nAttempting to stop the robot again"
69
- )
70
-
71
- self.stop_thread = None
72
- continue
73
- if self.state_machine.stopped:
74
- transition = self.state_machine.mission_stopped # type: ignore
75
- else:
76
- transition = self.state_machine.mission_paused # type: ignore
77
- break
78
-
79
- transition()
80
-
81
- def handle_stop_fail(self, retry_limit: int, error_message: ErrorMessage) -> bool:
82
- self._count_number_retries += 1
83
- if self._count_number_retries > retry_limit:
84
- self.state_machine.current_step.error_message = error_message
85
-
86
- self.logger.error(
87
- f"\nFailed to stop the robot after {retry_limit} attempts because: "
88
- f"{error_message.error_description}"
89
- f"\nBe aware that the robot may still be moving even though a stop has "
90
- "been attempted"
91
- )
92
-
93
- return True
94
- time.sleep(self.state_machine.sleep_time)
95
- return False