From 730167e3923bb95e75d5cae98279dc6b47e51ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Chirico=20Indreb=C3=B8?= Date: Wed, 15 Jan 2025 12:54:24 +0100 Subject: [PATCH] Fix inverted ID comparison --- src/isar/robot/robot.py | 266 +++--------------- src/isar/robot/robot_queue_utils.py | 22 ++ src/isar/robot/robot_start_mission_or_task.py | 99 +++++++ src/isar/robot/robot_status.py | 45 +++ src/isar/robot/robot_task_status.py | 115 ++++++++ src/isar/state_machine/states/monitor.py | 12 +- .../isar/state_machine/test_state_machine.py | 69 ++++- 7 files changed, 392 insertions(+), 236 deletions(-) create mode 100644 src/isar/robot/robot_queue_utils.py create mode 100644 src/isar/robot/robot_start_mission_or_task.py create mode 100644 src/isar/robot/robot_status.py create mode 100644 src/isar/robot/robot_task_status.py diff --git a/src/isar/robot/robot.py b/src/isar/robot/robot.py index ec30a38a..fad72551 100644 --- a/src/isar/robot/robot.py +++ b/src/isar/robot/robot.py @@ -1,37 +1,14 @@ import logging -import time -import queue +from threading import Event from injector import inject -from typing import Optional, Union, Any -from isar.config.settings import settings +from typing import Optional from isar.models.communication.queues.queues import Queues -from isar.models.communication.queues.status_queue import StatusQueue -from isar.state_machine.states_enum import States -from robot_interface.models.mission.status import RobotStatus -from isar.services.utilities.threaded_request import ( - ThreadedRequest, -) -from robot_interface.models.exceptions.robot_exceptions import ( - ErrorMessage, - RobotCommunicationException, - RobotCommunicationTimeoutException, - RobotException, - RobotTaskStatusException, -) -from robot_interface.models.mission.status import TaskStatus -from isar.models.communication.queues.queue_io import QueueIO +from isar.robot.robot_queue_utils import check_for_event +from isar.robot.robot_start_mission_or_task import RobotStartMissionOrTaskThread +from isar.robot.robot_status import RobotStatusThread +from isar.robot.robot_task_status import RobotTaskStatusThread from robot_interface.robot_interface import RobotInterface -from robot_interface.models.mission.mission import Mission -from robot_interface.models.mission.status import RobotStatus, TaskStatus -from robot_interface.models.mission.task import Task -from robot_interface.models.exceptions.robot_exceptions import ( - ErrorMessage, - RobotException, - RobotInfeasibleTaskException, - RobotInfeasibleMissionException, -) - class Robot(object): @@ -40,187 +17,26 @@ def __init__(self, queues: Queues, robot: RobotInterface): self.logger = logging.getLogger("robot") self.queues: Queues = queues self.robot: RobotInterface = robot - self.start_mission_thread: Optional[ThreadedRequest] = None - self.robot_status_thread: Optional[ThreadedRequest] = None - self.robot_task_status_thread: Optional[ThreadedRequest] = None - self.last_robot_status_poll_time: float = time.time() - self.current_status = None - self.request_status_failure_counter: int = 0 - self.request_status_failure_counter_limit: int = ( - settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT - ) - - def _trigger_event(self, queueio: QueueIO, data: any = None) -> Any: - queueio.input.put(data if data != None else True) - - def _check_shared_state(self, queueio: StatusQueue): - try: - return queueio.check() - except queue.Empty: - return None - - def _check_for_event(self, queueio: QueueIO) -> Any: - try: - return queueio.input.get(block=False) - except queue.Empty: - return None - - def run_robot_status_thread(self): - while True: - robot_status = self.robot.robot_status() - if ( - robot_status != self.current_status - and robot_status == RobotStatus.Offline - ): - self._trigger_event(self.queues.robot_offline) - if ( - robot_status != self.current_status - and robot_status != RobotStatus.Offline - ): - self._trigger_event(self.queues.robot_online) - self.current_status = robot_status - time.sleep(settings.FSM_SLEEP_TIME) - - def run_robot_task_status_thread(self): - while True: - current_state: Optional[States] = self._check_shared_state(self.queues.state) - - if not current_state in [States.Monitor, States.Paused, States.Stop]: - # Here we exit the thread as the mission is done - break - - current_task: Optional[Task] = self._check_shared_state( - self.queues.state_machine_current_task - ) - - failed_task_error: Optional[ErrorMessage] = None - - if current_task == None: - continue - - task_status: TaskStatus - try: - task_status = self.robot.task_status(current_task.id) - self.request_status_failure_counter = 0 - except ( - RobotCommunicationTimeoutException, - RobotCommunicationException, - ) as e: - self.request_status_failure_counter += 1 - - if ( - self.request_status_failure_counter - >= self.request_status_failure_counter_limit - ): - self.logger.error( - f"Failed to get task status " - f"{self.request_status_failure_counter} times because: " - f"{e.error_description}" - ) - self.logger.error( - f"Monitoring task failed because: {e.error_description}" - ) - failed_task_error = ErrorMessage( - error_reason=e.error_reason, - error_description=e.error_description, - ) - - else: - time.sleep(settings.FSM_SLEEP_TIME) - continue - - except RobotTaskStatusException as e: - failed_task_error = ErrorMessage( - error_reason=e.error_reason, - error_description=e.error_description, - ) - - except RobotException as e: - failed_task_error = ErrorMessage( - error_reason=e.error_reason, - error_description=e.error_description, - ) - - if failed_task_error != None: - self._trigger_event( - self.queues.robot_task_status_failed, - failed_task_error, - ) - task_status = TaskStatus.Failed - else: - self._trigger_event(self.queues.robot_task_status, task_status) - self.current_status = task_status - time.sleep(settings.FSM_SLEEP_TIME) + self.start_mission_thread: Optional[RobotStartMissionOrTaskThread] = None + self.robot_status_thread: Optional[RobotStatusThread] = None + self.robot_task_status_thread: Optional[RobotTaskStatusThread] = None + self.signal_thread_quitting: Event = Event() def stop(self) -> None: - if self.robot_status_thread: - self.robot_status_thread.wait_for_thread() - if self.robot_task_status_thread: - self.robot_task_status_thread.wait_for_thread() - if self.start_mission_thread: - self.start_mission_thread.wait_for_thread() + self.signal_thread_quitting.set() + if self.robot_status_thread != None and self.robot_status_thread.is_alive(): + self.robot_status_thread.join() + if ( + self.robot_task_status_thread != None + and self.robot_status_thread.is_alive() + ): + self.robot_task_status_thread.join() + if self.start_mission_thread != None and self.robot_status_thread.is_alive(): + self.start_mission_thread.join() self.robot_status_thread = None self.robot_task_status_thread = None self.start_mission_thread = None - - def run_start_mission_thread(self, mission_or_task: Mission | Task): - retries = 0 - started_mission = False - try: - while not started_mission: - try: - if settings.RUN_MISSION_BY_TASK: - self.robot.initiate_task( - mission_or_task - ) - else: - self.robot.initiate_mission(mission_or_task) - except RobotException as e: - retries += 1 - self.logger.warning( - f"Initiating failed #: {str(retries)} " - f"because: {e.error_description}" - ) - - if retries >= settings.INITIATE_FAILURE_COUNTER_LIMIT: - error_description = ( - f"Mission will be cancelled after failing to initiate " - f"{settings.INITIATE_FAILURE_COUNTER_LIMIT} times because: " - f"{e.error_description}" - ) - - self._trigger_event( - self.queues.robot_mission_failed, - ErrorMessage( - error_reason=e.error_reason, - error_description=error_description, - ), - ) - started_mission = True - except RobotInfeasibleTaskException as e: - error_description = ( - f"Failed to initiate task " - f"{str(mission_or_task.id)[:8]} after retrying " - f"{retries} times because: " - f"{e.error_description}" - ) - self._trigger_event( - self.queues.robot_task_status_failed, - ErrorMessage( - error_reason=e.error_reason, error_description=error_description - ), - ) - except RobotInfeasibleMissionException as e: - self._trigger_event( - self.queues.robot_mission_failed, - ErrorMessage( - error_reason=e.error_reason, error_description=e.error_description - ), - ) - self.robot_task_status_thread = ThreadedRequest( - request_func=self.run_robot_task_status_thread - ) - self.robot_task_status_thread.start_thread(name="Robot task status thread") + self.signal_thread_suicide = False def handle_stop_mission(self): return NotImplementedError() @@ -229,33 +45,43 @@ def handle_pause_mission(self): return NotImplementedError() def run(self) -> None: - self.robot_status_thread = ThreadedRequest( - request_func=self.run_robot_status_thread + self.robot_status_thread = RobotStatusThread( + self.queues, self.robot, self.signal_thread_quitting ) - self.robot_status_thread.start_thread(name="Robot status thread") + self.robot_status_thread.start() + + self.robot_task_status_thread = RobotTaskStatusThread( + self.queues, self.robot, self.signal_thread_quitting + ) + self.robot_task_status_thread.start() + while True: - start_mission_or_task = self._check_for_event( + if self.signal_thread_quitting.is_set(): + break + start_mission_or_task = check_for_event( self.queues.state_machine_start_mission ) if start_mission_or_task != None: if ( self.start_mission_thread != None - and self.start_mission_thread._is_thread_alive() + and self.start_mission_thread.is_alive() ): - self.start_mission_thread.wait_for_thread() - self.start_mission_thread = ThreadedRequest( - request_func=self.run_start_mission_thread, - ) - self.start_mission_thread.start_thread( - start_mission_or_task, name="Robot start mission thread" + self.start_mission_thread.join() + self.start_mission_thread = RobotStartMissionOrTaskThread( + self.queues, + self.robot, + self.signal_thread_quitting, + start_mission_or_task, ) + self.start_mission_thread.start() # TODO: consider what happens when we get another start_mission while the start_mission_thread is running. - if self._check_for_event(self.queues.state_machine_stop_mission): + if check_for_event(self.queues.state_machine_stop_mission): if ( self.start_mission_thread != None - and self.start_mission_thread._is_thread_alive() + and self.start_mission_thread.is_alive() ): - self.start_mission_thread.wait_for_thread() + self.start_mission_thread.join() self.handle_stop_mission() - if self._check_for_event(self.queues.state_machine_pause_mission): + if check_for_event(self.queues.state_machine_pause_mission): self.handle_pause_mission() + self.logger.info("Exiting robot service main thread") diff --git a/src/isar/robot/robot_queue_utils.py b/src/isar/robot/robot_queue_utils.py new file mode 100644 index 00000000..4bc1db8c --- /dev/null +++ b/src/isar/robot/robot_queue_utils.py @@ -0,0 +1,22 @@ +import queue +from typing import Any +from isar.models.communication.queues.status_queue import StatusQueue +from isar.models.communication.queues.queue_io import QueueIO + + +def trigger_event(queueio: QueueIO, data: any = None) -> Any: + queueio.input.put(data if data != None else True) + + +def check_shared_state(queueio: StatusQueue): + try: + return queueio.check() + except queue.Empty: + return None + + +def check_for_event(queueio: QueueIO) -> Any: + try: + return queueio.input.get(block=False) + except queue.Empty: + return None diff --git a/src/isar/robot/robot_start_mission_or_task.py b/src/isar/robot/robot_start_mission_or_task.py new file mode 100644 index 00000000..d94a61fa --- /dev/null +++ b/src/isar/robot/robot_start_mission_or_task.py @@ -0,0 +1,99 @@ +import logging +import time +from threading import Event, Thread +from injector import inject +from typing import Optional +from isar.config.settings import settings +from isar.models.communication.queues.queues import Queues +from isar.robot.robot_queue_utils import trigger_event +from isar.services.utilities.threaded_request import ( + ThreadedRequest, +) +from robot_interface.models.exceptions.robot_exceptions import ( + ErrorMessage, + RobotException, +) +from robot_interface.robot_interface import RobotInterface + +from robot_interface.models.mission.mission import Mission +from robot_interface.models.mission.task import Task +from robot_interface.models.exceptions.robot_exceptions import ( + ErrorMessage, + RobotException, + RobotInfeasibleTaskException, + RobotInfeasibleMissionException, +) + + +class RobotStartMissionOrTaskThread(Thread): + + def __init__( + self, + queues: Queues, + robot: RobotInterface, + signal_thread_quitting: Event, + mission_or_task: Mission | Task, + ): + self.logger = logging.getLogger("robot") + self.queues: Queues = queues + self.robot: RobotInterface = robot + self.start_mission_thread: Optional[ThreadedRequest] = None + self.signal_thread_quitting: Event = signal_thread_quitting + self.mission_or_task = mission_or_task + Thread.__init__(self, name="Robot start mission or task thread") + + def run(self): + retries = 0 + started_mission = False + try: + while not started_mission: + if self.signal_thread_quitting.wait(0): + return + try: + if settings.RUN_MISSION_BY_TASK: + self.robot.initiate_task(self.mission_or_task) + else: + self.robot.initiate_mission(self.mission_or_task) + except RobotException as e: + retries += 1 + self.logger.warning( + f"Initiating failed #: {str(retries)} " + f"because: {e.error_description}" + ) + + if retries >= settings.INITIATE_FAILURE_COUNTER_LIMIT: + error_description = ( + f"Mission will be cancelled after failing to initiate " + f"{settings.INITIATE_FAILURE_COUNTER_LIMIT} times because: " + f"{e.error_description}" + ) + + trigger_event( + self.queues.robot_mission_failed, + ErrorMessage( + error_reason=e.error_reason, + error_description=error_description, + ), + ) + started_mission = True + except RobotInfeasibleTaskException as e: + error_description = ( + f"Failed to initiate task " + f"{str(self.mission_or_task.id)[:8]} after retrying " + f"{retries} times because: " + f"{e.error_description}" + ) + trigger_event( + self.queues.robot_task_status_failed, + ErrorMessage( + error_reason=e.error_reason, error_description=error_description + ), + ) + except RobotInfeasibleMissionException as e: + trigger_event( + self.queues.robot_mission_failed, + ErrorMessage( + error_reason=e.error_reason, error_description=e.error_description + ), + ) + trigger_event(self.queues.robot_mission_started) diff --git a/src/isar/robot/robot_status.py b/src/isar/robot/robot_status.py new file mode 100644 index 00000000..ef348b23 --- /dev/null +++ b/src/isar/robot/robot_status.py @@ -0,0 +1,45 @@ +import logging +import time +from threading import Event, Thread +from typing import Optional +from isar.config.settings import settings +from isar.models.communication.queues.queues import Queues +from isar.robot.robot_queue_utils import trigger_event +from isar.services.utilities.threaded_request import ( + ThreadedRequest, +) +from robot_interface.models.mission.status import RobotStatus +from robot_interface.robot_interface import RobotInterface + + +class RobotStatusThread(Thread): + + def __init__( + self, queues: Queues, robot: RobotInterface, signal_thread_quitting: Event + ): + self.logger = logging.getLogger("robot") + self.queues: Queues = queues + self.robot: RobotInterface = robot + self.start_mission_thread: Optional[ThreadedRequest] = None + self.signal_thread_quitting: Event = signal_thread_quitting + self.current_status: Optional[RobotStatus] = None + Thread.__init__(self, name="Robot status thread") + + def run(self): + while True: + if self.signal_thread_quitting.is_set(): + break + robot_status = self.robot.robot_status() + if ( + robot_status != self.current_status + and robot_status == RobotStatus.Offline + ): + trigger_event(self.queues.robot_offline) + if ( + robot_status != self.current_status + and robot_status != RobotStatus.Offline + ): + trigger_event(self.queues.robot_online) + self.current_status = robot_status + time.sleep(settings.FSM_SLEEP_TIME) + self.logger.info("Exiting robot status thread") diff --git a/src/isar/robot/robot_task_status.py b/src/isar/robot/robot_task_status.py new file mode 100644 index 00000000..5ffb621c --- /dev/null +++ b/src/isar/robot/robot_task_status.py @@ -0,0 +1,115 @@ +import logging +import time +from threading import Event, Thread +from typing import Optional +from isar.config.settings import settings +from isar.models.communication.queues.queues import Queues +from isar.robot.robot_queue_utils import check_shared_state, trigger_event +from isar.services.utilities.threaded_request import ( + ThreadedRequest, +) +from isar.state_machine.states_enum import States +from robot_interface.models.exceptions.robot_exceptions import ( + ErrorMessage, + RobotCommunicationException, + RobotCommunicationTimeoutException, + RobotException, + RobotTaskStatusException, +) +from robot_interface.models.mission.status import RobotStatus, TaskStatus +from robot_interface.models.mission.task import Task +from robot_interface.robot_interface import RobotInterface + + +class RobotTaskStatusThread(Thread): + + def __init__( + self, queues: Queues, robot: RobotInterface, signal_thread_quitting: Event + ): + self.logger = logging.getLogger("robot") + self.queues: Queues = queues + self.robot: RobotInterface = robot + self.start_mission_thread: Optional[ThreadedRequest] = None + self.signal_thread_quitting: Event = signal_thread_quitting + self.current_status: Optional[RobotStatus] = None + self.request_status_failure_counter: int = 0 + self.request_status_failure_counter_limit: int = ( + settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT + ) + Thread.__init__(self, name="Robot task status thread") + + def run(self): + while True: + if self.signal_thread_quitting.is_set(): + break + current_state: Optional[States] = check_shared_state(self.queues.state) + + if not current_state in [States.Monitor, States.Paused, States.Stop]: + time.sleep(settings.FSM_SLEEP_TIME) + continue + + current_task: Optional[Task] = check_shared_state( + self.queues.state_machine_current_task + ) + + failed_task_error: Optional[ErrorMessage] = None + + if current_task == None: + continue + + task_status: TaskStatus + try: + task_status = self.robot.task_status(current_task.id) + # TODO: verify that current_task is not outdated between here and checking in monitor + # TODO: only try to get task status if we have a new task_id or if the last task status was not success/fail + self.request_status_failure_counter = 0 + except ( + RobotCommunicationTimeoutException, + RobotCommunicationException, + ) as e: + self.request_status_failure_counter += 1 + + if ( + self.request_status_failure_counter + >= self.request_status_failure_counter_limit + ): + self.logger.error( + f"Failed to get task status " + f"{self.request_status_failure_counter} times because: " + f"{e.error_description}" + ) + self.logger.error( + f"Monitoring task failed because: {e.error_description}" + ) + failed_task_error = ErrorMessage( + error_reason=e.error_reason, + error_description=e.error_description, + ) + + else: + time.sleep(settings.FSM_SLEEP_TIME) + continue + + except RobotTaskStatusException as e: + failed_task_error = ErrorMessage( + error_reason=e.error_reason, + error_description=e.error_description, + ) + + except RobotException as e: + failed_task_error = ErrorMessage( + error_reason=e.error_reason, + error_description=e.error_description, + ) + + if failed_task_error != None: + trigger_event( + self.queues.robot_task_status_failed, + failed_task_error, + ) + task_status = TaskStatus.Failed + else: + trigger_event(self.queues.robot_task_status, task_status) + self.current_status = task_status + time.sleep(settings.FSM_SLEEP_TIME) + self.logger.info("Exiting robot task status thread") diff --git a/src/isar/state_machine/states/monitor.py b/src/isar/state_machine/states/monitor.py index ba433e13..3e19358b 100644 --- a/src/isar/state_machine/states/monitor.py +++ b/src/isar/state_machine/states/monitor.py @@ -40,10 +40,16 @@ def stop(self) -> None: return def _run(self) -> None: + mission_has_started: bool = False transition: Callable while True: - if self.state_machine.has_mission_started(): - continue + if not mission_has_started: + # TODO: put stop and pause mission check before this + if self.state_machine.has_mission_started(): + mission_has_started = True + else: + # time.sleep(settings.FSM_SLEEP_TIME) + continue mission_failed = self.state_machine.has_mission_failed() if mission_failed != None: @@ -143,7 +149,7 @@ def _queue_inspections_for_upload( inspection: Inspection = self.state_machine.robot.get_inspection( task=current_task ) - if current_task.inspection_id == inspection.id: + if current_task.inspection_id != inspection.id: self.logger.warning( f"The inspection_id of task ({current_task.inspection_id}) " f"and result ({inspection.id}) is not matching. " diff --git a/tests/isar/state_machine/test_state_machine.py b/tests/isar/state_machine/test_state_machine.py index db827f8e..293890ed 100644 --- a/tests/isar/state_machine/test_state_machine.py +++ b/tests/isar/state_machine/test_state_machine.py @@ -8,6 +8,7 @@ from pytest_mock import MockerFixture from isar.models.communication.queues.queues import Queues +from isar.robot.robot import Robot from isar.services.utilities.scheduling_utilities import SchedulingUtilities from isar.state_machine.state_machine import StateMachine, main from isar.state_machine.states.idle import Idle @@ -21,6 +22,7 @@ from robot_interface.models.mission.mission import Mission from robot_interface.models.mission.status import TaskStatus from robot_interface.models.mission.task import ReturnToHome, TakeImage, Task +from robot_interface.robot_interface import RobotInterface from robot_interface.telemetry.mqtt_client import MqttClientInterface from tests.mocks.pose import MockPose from tests.mocks.robot_interface import ( @@ -55,6 +57,24 @@ def __init__(self, injector) -> None: self._thread.start() +class RobotServiceThread(object): + def __init__(self, injector) -> None: + self.injector: Injector = injector + + def start(self): + self.robot_service: Robot = Robot( + queues=self.injector.get(Queues), + robot=self.injector.get(RobotInterface), + ) + self._thread: Thread = Thread(target=self.robot_service.run) + self._thread.daemon = True + self._thread.start() + + def teardown(self): + self.robot_service.stop() + self._thread.join() + + @pytest.fixture def state_machine_thread(injector) -> StateMachineThread: return StateMachineThread(injector) @@ -65,6 +85,13 @@ def uploader_thread(injector) -> UploaderThread: return UploaderThread(injector=injector) +@pytest.fixture +def robot_service_thread(injector): + robot_service_thread: RobotServiceThread = RobotServiceThread(injector=injector) + yield robot_service_thread + robot_service_thread.teardown() + + def test_initial_off(state_machine) -> None: assert state_machine.state == "off" @@ -83,8 +110,9 @@ def test_reset_state_machine(state_machine) -> None: def test_state_machine_transitions_when_running_mission_by_task( - injector, state_machine_thread + injector, state_machine_thread, robot_service_thread ) -> None: + robot_service_thread.start() task_1: Task = TakeImage( target=MockPose.default_pose().position, robot_pose=MockPose.default_pose() ) @@ -96,7 +124,7 @@ def test_state_machine_transitions_when_running_mission_by_task( scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities) scheduling_utilities.start_mission(mission=mission, initial_pose=None) - time.sleep(0.01) + time.sleep(2) assert state_machine_thread.state_machine.transitions_list == deque( [ @@ -109,8 +137,9 @@ def test_state_machine_transitions_when_running_mission_by_task( def test_state_machine_transitions_when_running_full_mission( - injector, state_machine_thread + injector, state_machine_thread, robot_service_thread ) -> None: + robot_service_thread.start() state_machine_thread.state_machine.run_mission_by_task = False state_machine_thread.start() @@ -122,7 +151,9 @@ def test_state_machine_transitions_when_running_full_mission( scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities) scheduling_utilities.start_mission(mission=mission, initial_pose=None) - time.sleep(0.11) # Slightly more than the StateMachine sleep time + time.sleep(1) # Slightly more than the StateMachine sleep time + + # TODO: the cleanup hangs on this one assert state_machine_thread.state_machine.transitions_list == deque( [ @@ -134,8 +165,9 @@ def test_state_machine_transitions_when_running_full_mission( def test_state_machine_failed_dependency( - injector, state_machine_thread, mocker + injector, state_machine_thread, robot_service_thread, mocker ) -> None: + robot_service_thread.start() task_1: Task = TakeImage( target=MockPose.default_pose().position, robot_pose=MockPose.default_pose() ) @@ -146,11 +178,13 @@ def test_state_machine_failed_dependency( state_machine_thread.state_machine.run_mission_by_task = True + # TODO: cleanup on this one hangs, but it is cancellable + state_machine_thread.start() scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities) scheduling_utilities.start_mission(mission=mission, initial_pose=None) - time.sleep(0.01) + time.sleep(5) assert state_machine_thread.state_machine.transitions_list == deque( [ @@ -163,8 +197,9 @@ def test_state_machine_failed_dependency( def test_state_machine_with_successful_collection( - injector, state_machine_thread, uploader_thread + injector, state_machine_thread, robot_service_thread ) -> None: + robot_service_thread.start() state_machine_thread.start() storage_mock: StorageInterface = injector.get(List[StorageInterface])[0] @@ -187,12 +222,15 @@ def test_state_machine_with_successful_collection( def test_state_machine_with_unsuccessful_collection( - injector, mocker, state_machine_thread + injector, mocker, state_machine_thread, robot_service_thread ) -> None: + robot_service_thread.start() storage_mock: StorageInterface = injector.get(List[StorageInterface])[0] mocker.patch.object(MockRobot, "get_inspection", return_value=[]) + mocker.patch.object(Idle, "_is_ready_to_poll_for_status", return_value=True) + state_machine_thread.start() mission: Mission = Mission(name="Dummy misson", tasks=[MockTask.take_image()]) @@ -215,9 +253,10 @@ def test_state_machine_with_unsuccessful_collection( def test_state_machine_with_successful_mission_stop( injector: Injector, - mocker: MockerFixture, + robot_service_thread: RobotServiceThread, state_machine_thread: StateMachineThread, ) -> None: + robot_service_thread.start() state_machine_thread.start() mission: Mission = Mission( @@ -239,7 +278,9 @@ def test_state_machine_with_unsuccessful_mission_stop( mocker: MockerFixture, state_machine_thread: StateMachineThread, caplog: pytest.LogCaptureFixture, + robot_service_thread: RobotServiceThread, ) -> None: + robot_service_thread.start() mission: Mission = Mission(name="Dummy misson", tasks=[MockTask.take_image()]) scheduling_utilities: SchedulingUtilities = injector.get(SchedulingUtilities) @@ -264,8 +305,10 @@ def test_state_machine_with_unsuccessful_mission_stop( ) -def test_state_machine_idle_to_offline_to_idle(mocker, state_machine_thread) -> None: - +def test_state_machine_idle_to_offline_to_idle( + mocker, state_machine_thread, robot_service_thread +) -> None: + robot_service_thread.start() # Robot status check happens every 5 seconds by default, so we mock the behavior # to poll for status imediately mocker.patch.object(Idle, "_is_ready_to_poll_for_status", return_value=True) @@ -280,9 +323,9 @@ def test_state_machine_idle_to_offline_to_idle(mocker, state_machine_thread) -> def test_state_machine_idle_to_blocked_protective_stop_to_idle( - mocker, state_machine_thread + mocker, state_machine_thread, robot_service_thread ) -> None: - + robot_service_thread.start() # Robot status check happens every 5 seconds by default, so we mock the behavior # to poll for status imediately mocker.patch.object(Idle, "_is_ready_to_poll_for_status", return_value=True)