isar 1.20.2__py3-none-any.whl → 1.34.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- isar/apis/api.py +135 -86
- isar/apis/models/__init__.py +0 -1
- isar/apis/models/models.py +21 -11
- isar/apis/models/start_mission_definition.py +115 -170
- isar/apis/robot_control/robot_controller.py +41 -0
- isar/apis/schedule/scheduling_controller.py +123 -187
- isar/apis/security/authentication.py +5 -5
- isar/config/certs/ca-cert.pem +33 -31
- isar/config/keyvault/keyvault_service.py +4 -2
- isar/config/log.py +45 -40
- isar/config/logging.conf +16 -31
- isar/config/open_telemetry.py +102 -0
- isar/config/settings.py +74 -117
- isar/eventhandlers/eventhandler.py +123 -0
- isar/models/events.py +184 -0
- isar/models/status.py +22 -0
- isar/modules.py +117 -200
- isar/robot/robot.py +383 -0
- isar/robot/robot_battery.py +60 -0
- isar/robot/robot_monitor_mission.py +357 -0
- isar/robot/robot_pause_mission.py +74 -0
- isar/robot/robot_resume_mission.py +67 -0
- isar/robot/robot_start_mission.py +66 -0
- isar/robot/robot_status.py +61 -0
- isar/robot/robot_stop_mission.py +68 -0
- isar/robot/robot_upload_inspection.py +75 -0
- isar/script.py +58 -41
- isar/services/service_connections/mqtt/mqtt_client.py +47 -11
- isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
- isar/services/service_connections/mqtt/robot_info_publisher.py +3 -3
- isar/services/service_connections/persistent_memory.py +69 -0
- isar/services/utilities/mqtt_utilities.py +93 -0
- isar/services/utilities/robot_utilities.py +20 -0
- isar/services/utilities/scheduling_utilities.py +386 -100
- isar/state_machine/state_machine.py +242 -539
- isar/state_machine/states/__init__.py +0 -8
- isar/state_machine/states/await_next_mission.py +114 -0
- isar/state_machine/states/blocked_protective_stop.py +60 -0
- isar/state_machine/states/going_to_lockdown.py +95 -0
- isar/state_machine/states/going_to_recharging.py +92 -0
- isar/state_machine/states/home.py +115 -0
- isar/state_machine/states/intervention_needed.py +77 -0
- isar/state_machine/states/lockdown.py +38 -0
- isar/state_machine/states/maintenance.py +43 -0
- isar/state_machine/states/monitor.py +137 -247
- isar/state_machine/states/offline.py +51 -53
- isar/state_machine/states/paused.py +92 -23
- isar/state_machine/states/pausing.py +48 -0
- isar/state_machine/states/pausing_return_home.py +48 -0
- isar/state_machine/states/recharging.py +80 -0
- isar/state_machine/states/resuming.py +57 -0
- isar/state_machine/states/resuming_return_home.py +64 -0
- isar/state_machine/states/return_home_paused.py +109 -0
- isar/state_machine/states/returning_home.py +217 -0
- isar/state_machine/states/stopping.py +69 -0
- isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
- isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
- isar/state_machine/states/stopping_go_to_recharge.py +51 -0
- isar/state_machine/states/stopping_paused_mission.py +36 -0
- isar/state_machine/states/stopping_paused_return_home.py +59 -0
- isar/state_machine/states/stopping_return_home.py +59 -0
- isar/state_machine/states/unknown_status.py +74 -0
- isar/state_machine/states_enum.py +23 -5
- isar/state_machine/transitions/mission.py +225 -0
- isar/state_machine/transitions/return_home.py +108 -0
- isar/state_machine/transitions/robot_status.py +87 -0
- isar/state_machine/utils/common_event_handlers.py +138 -0
- isar/storage/blob_storage.py +70 -52
- isar/storage/local_storage.py +25 -12
- isar/storage/storage_interface.py +28 -7
- isar/storage/uploader.py +174 -55
- isar/storage/utilities.py +32 -29
- {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/METADATA +119 -123
- isar-1.34.13.dist-info/RECORD +120 -0
- {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/WHEEL +1 -1
- {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/entry_points.txt +1 -0
- robot_interface/models/exceptions/robot_exceptions.py +91 -41
- robot_interface/models/inspection/__init__.py +0 -13
- robot_interface/models/inspection/inspection.py +42 -33
- robot_interface/models/mission/mission.py +14 -15
- robot_interface/models/mission/status.py +20 -26
- robot_interface/models/mission/task.py +154 -121
- robot_interface/models/robots/battery_state.py +6 -0
- robot_interface/models/robots/media.py +13 -0
- robot_interface/models/robots/robot_model.py +7 -7
- robot_interface/robot_interface.py +119 -84
- robot_interface/telemetry/mqtt_client.py +74 -12
- robot_interface/telemetry/payloads.py +91 -13
- robot_interface/utilities/json_service.py +7 -1
- isar/config/configuration_error.py +0 -2
- isar/config/keyvault/keyvault_error.py +0 -2
- isar/config/predefined_mission_definition/__init__.py +0 -0
- isar/config/predefined_mission_definition/default_exr.json +0 -51
- isar/config/predefined_mission_definition/default_mission.json +0 -91
- isar/config/predefined_mission_definition/default_turtlebot.json +0 -124
- isar/config/predefined_missions/__init__.py +0 -0
- isar/config/predefined_missions/default.json +0 -92
- isar/config/predefined_missions/default_turtlebot.json +0 -110
- isar/config/predefined_poses/__init__.py +0 -0
- isar/config/predefined_poses/predefined_poses.py +0 -616
- isar/config/settings.env +0 -25
- isar/mission_planner/__init__.py +0 -0
- isar/mission_planner/local_planner.py +0 -82
- isar/mission_planner/mission_planner_interface.py +0 -26
- isar/mission_planner/sequential_task_selector.py +0 -23
- isar/mission_planner/task_selector_interface.py +0 -31
- isar/models/communication/__init__.py +0 -0
- isar/models/communication/message.py +0 -12
- isar/models/communication/queues/__init__.py +0 -4
- isar/models/communication/queues/queue_io.py +0 -12
- isar/models/communication/queues/queue_timeout_error.py +0 -2
- isar/models/communication/queues/queues.py +0 -19
- isar/models/communication/queues/status_queue.py +0 -20
- isar/models/mission_metadata/__init__.py +0 -0
- isar/services/auth/__init__.py +0 -0
- isar/services/auth/azure_credentials.py +0 -14
- isar/services/readers/__init__.py +0 -0
- isar/services/readers/base_reader.py +0 -37
- isar/services/service_connections/request_handler.py +0 -153
- isar/services/service_connections/stid/__init__.py +0 -0
- isar/services/utilities/queue_utilities.py +0 -39
- isar/services/utilities/threaded_request.py +0 -68
- isar/state_machine/states/idle.py +0 -85
- isar/state_machine/states/initialize.py +0 -71
- isar/state_machine/states/initiate.py +0 -142
- isar/state_machine/states/off.py +0 -18
- isar/state_machine/states/stop.py +0 -95
- isar/storage/slimm_storage.py +0 -191
- isar-1.20.2.dist-info/RECORD +0 -116
- robot_interface/models/initialize/__init__.py +0 -1
- robot_interface/models/initialize/initialize_params.py +0 -9
- robot_interface/models/mission/step.py +0 -234
- {isar-1.20.2.dist-info → isar-1.34.13.dist-info/licenses}/LICENSE +0 -0
- {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/top_level.txt +0 -0
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
from alitra import Frame
|
|
5
|
-
from injector import inject
|
|
6
|
-
|
|
7
|
-
from isar.config.settings import settings
|
|
8
|
-
from isar.mission_planner.mission_planner_interface import (
|
|
9
|
-
MissionNotFoundError,
|
|
10
|
-
MissionPlannerError,
|
|
11
|
-
MissionPlannerInterface,
|
|
12
|
-
)
|
|
13
|
-
from isar.services.readers.base_reader import BaseReader, BaseReaderError
|
|
14
|
-
from robot_interface.models.mission.mission import Mission
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger("api")
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class LocalPlanner(MissionPlannerInterface):
|
|
20
|
-
@inject
|
|
21
|
-
def __init__(self):
|
|
22
|
-
self.predefined_mission_folder = Path(settings.PREDEFINED_MISSIONS_FOLDER)
|
|
23
|
-
|
|
24
|
-
def get_mission(self, mission_id) -> Mission:
|
|
25
|
-
missions: dict = self.get_predefined_missions()
|
|
26
|
-
if missions is None:
|
|
27
|
-
raise MissionPlannerError("There were no predefined missions")
|
|
28
|
-
try:
|
|
29
|
-
mission: Mission = missions[mission_id]["mission"]
|
|
30
|
-
return mission
|
|
31
|
-
except KeyError as e:
|
|
32
|
-
raise MissionNotFoundError(
|
|
33
|
-
f"Could not get mission : {mission_id} - does not exist {e}"
|
|
34
|
-
) from e
|
|
35
|
-
except Exception as e:
|
|
36
|
-
raise MissionPlannerError(f"Could not get mission : {mission_id}") from e
|
|
37
|
-
|
|
38
|
-
@staticmethod
|
|
39
|
-
def read_mission_from_file(mission_path: Path) -> Mission:
|
|
40
|
-
mission_dict: dict = BaseReader.read_json(location=mission_path)
|
|
41
|
-
mission: Mission = BaseReader.dict_to_dataclass(
|
|
42
|
-
dataclass_dict=mission_dict,
|
|
43
|
-
target_dataclass=Mission,
|
|
44
|
-
cast_config=[Frame],
|
|
45
|
-
strict_config=True,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
return mission
|
|
49
|
-
|
|
50
|
-
def get_predefined_missions(self) -> dict:
|
|
51
|
-
missions: dict = {}
|
|
52
|
-
invalid_mission_ids: list = []
|
|
53
|
-
json_files = self.predefined_mission_folder.glob("*.json")
|
|
54
|
-
for file in json_files:
|
|
55
|
-
mission_name = file.stem
|
|
56
|
-
path_to_file = self.predefined_mission_folder.joinpath(file.name)
|
|
57
|
-
try:
|
|
58
|
-
mission: Mission = self.read_mission_from_file(path_to_file)
|
|
59
|
-
except BaseReaderError as e:
|
|
60
|
-
logger.warning(
|
|
61
|
-
f"Failed to read predefined mission {path_to_file} \n {e}"
|
|
62
|
-
)
|
|
63
|
-
continue
|
|
64
|
-
if mission.id in invalid_mission_ids:
|
|
65
|
-
logger.warning(
|
|
66
|
-
f"Duplicate mission id {mission.id} : {path_to_file.as_posix()}"
|
|
67
|
-
)
|
|
68
|
-
elif mission.id in missions:
|
|
69
|
-
conflicting_file_path = missions[mission.id]["file"]
|
|
70
|
-
logger.warning(
|
|
71
|
-
f"Duplicate mission id {mission.id} : {path_to_file.as_posix()}"
|
|
72
|
-
+ f" and {conflicting_file_path}"
|
|
73
|
-
)
|
|
74
|
-
invalid_mission_ids.append(mission.id)
|
|
75
|
-
missions.pop(mission.id)
|
|
76
|
-
else:
|
|
77
|
-
missions[mission.id] = {
|
|
78
|
-
"name": mission_name,
|
|
79
|
-
"file": path_to_file.as_posix(),
|
|
80
|
-
"mission": mission,
|
|
81
|
-
}
|
|
82
|
-
return missions
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from abc import ABCMeta, abstractmethod
|
|
2
|
-
|
|
3
|
-
from robot_interface.models.mission.mission import Mission
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class MissionPlannerInterface(metaclass=ABCMeta):
|
|
7
|
-
@abstractmethod
|
|
8
|
-
def get_mission(self, mission_id: str) -> Mission:
|
|
9
|
-
"""
|
|
10
|
-
Parameters
|
|
11
|
-
----------
|
|
12
|
-
mission_id : int
|
|
13
|
-
|
|
14
|
-
Returns
|
|
15
|
-
-------
|
|
16
|
-
mission : Mission
|
|
17
|
-
"""
|
|
18
|
-
pass
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class MissionPlannerError(Exception):
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class MissionNotFoundError(Exception):
|
|
26
|
-
pass
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
from typing import Iterator, List
|
|
2
|
-
|
|
3
|
-
from isar.mission_planner.task_selector_interface import (
|
|
4
|
-
TaskSelectorInterface,
|
|
5
|
-
TaskSelectorStop,
|
|
6
|
-
)
|
|
7
|
-
from robot_interface.models.mission.task import Task
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class SequentialTaskSelector(TaskSelectorInterface):
|
|
11
|
-
def __init__(self) -> None:
|
|
12
|
-
super().__init__()
|
|
13
|
-
self._iterator: Iterator = None
|
|
14
|
-
|
|
15
|
-
def initialize(self, tasks: List[Task]) -> None:
|
|
16
|
-
super().initialize(tasks=tasks)
|
|
17
|
-
self._iterator = iter(self.tasks)
|
|
18
|
-
|
|
19
|
-
def next_task(self) -> Task:
|
|
20
|
-
try:
|
|
21
|
-
return next(self._iterator)
|
|
22
|
-
except StopIteration:
|
|
23
|
-
raise TaskSelectorStop
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from abc import ABCMeta, abstractmethod
|
|
2
|
-
from typing import List
|
|
3
|
-
|
|
4
|
-
from robot_interface.models.mission.task import Task
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class TaskSelectorInterface(metaclass=ABCMeta):
|
|
8
|
-
def __init__(self) -> None:
|
|
9
|
-
self.tasks: List[Task] = None
|
|
10
|
-
|
|
11
|
-
def initialize(self, tasks: List[Task]) -> None:
|
|
12
|
-
self.tasks = tasks
|
|
13
|
-
|
|
14
|
-
@abstractmethod
|
|
15
|
-
def next_task(self) -> Task:
|
|
16
|
-
"""
|
|
17
|
-
Returns
|
|
18
|
-
-------
|
|
19
|
-
Task
|
|
20
|
-
Returns the next task from the list of tasks
|
|
21
|
-
|
|
22
|
-
Raises
|
|
23
|
-
------
|
|
24
|
-
TaskSelectorStop
|
|
25
|
-
If all tasks have been selected, and the task selection process is complete.
|
|
26
|
-
"""
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class TaskSelectorStop(Exception):
|
|
31
|
-
pass
|
|
File without changes
|
|
@@ -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,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
|
isar/services/auth/__init__.py
DELETED
|
File without changes
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
from azure.core.exceptions import ClientAuthenticationError
|
|
4
|
-
from azure.identity import DefaultAzureCredential
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class AzureCredentials:
|
|
8
|
-
@staticmethod
|
|
9
|
-
def get_azure_credentials():
|
|
10
|
-
try:
|
|
11
|
-
return DefaultAzureCredential()
|
|
12
|
-
except ClientAuthenticationError as e:
|
|
13
|
-
logging.error(e.message)
|
|
14
|
-
raise e
|
|
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
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Any, Optional
|
|
3
|
-
|
|
4
|
-
import requests
|
|
5
|
-
from requests.exceptions import ConnectionError, HTTPError, RequestException, Timeout
|
|
6
|
-
from requests.models import Response
|
|
7
|
-
|
|
8
|
-
from isar.config.settings import settings
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class RequestHandler:
|
|
12
|
-
def __init__(self):
|
|
13
|
-
self.logger = logging.getLogger("request_handler")
|
|
14
|
-
|
|
15
|
-
def base_request(
|
|
16
|
-
self,
|
|
17
|
-
url: str,
|
|
18
|
-
method: str,
|
|
19
|
-
json_body: Any,
|
|
20
|
-
timeout: float,
|
|
21
|
-
auth: tuple,
|
|
22
|
-
headers: Optional[dict] = None,
|
|
23
|
-
data: Optional[dict] = None,
|
|
24
|
-
params: Optional[dict] = None,
|
|
25
|
-
**kwargs,
|
|
26
|
-
) -> Response:
|
|
27
|
-
try:
|
|
28
|
-
response = requests.request(
|
|
29
|
-
url=url,
|
|
30
|
-
method=method,
|
|
31
|
-
auth=auth,
|
|
32
|
-
headers=headers,
|
|
33
|
-
timeout=timeout,
|
|
34
|
-
json=json_body,
|
|
35
|
-
data=data,
|
|
36
|
-
params=params,
|
|
37
|
-
**kwargs,
|
|
38
|
-
)
|
|
39
|
-
except Timeout as e:
|
|
40
|
-
self.logger.exception("Timeout exception")
|
|
41
|
-
raise RequestException from e
|
|
42
|
-
except ConnectionError as e:
|
|
43
|
-
self.logger.exception("Connection error")
|
|
44
|
-
raise RequestException from e
|
|
45
|
-
except RequestException as e:
|
|
46
|
-
raise RequestException from e
|
|
47
|
-
except Exception as e:
|
|
48
|
-
self.logger.exception("An unhandled exception occurred during a request")
|
|
49
|
-
raise RequestException from e
|
|
50
|
-
try:
|
|
51
|
-
response.raise_for_status()
|
|
52
|
-
except HTTPError:
|
|
53
|
-
self.logger.exception(
|
|
54
|
-
f"Http error. Http status code= {response.status_code}, Content: {response.content}"
|
|
55
|
-
)
|
|
56
|
-
raise
|
|
57
|
-
return response
|
|
58
|
-
|
|
59
|
-
def get(
|
|
60
|
-
self,
|
|
61
|
-
url: str,
|
|
62
|
-
json_body=None,
|
|
63
|
-
request_timeout: float = settings.REQUEST_TIMEOUT,
|
|
64
|
-
auth: Optional[tuple] = None,
|
|
65
|
-
headers: Optional[dict] = None,
|
|
66
|
-
data: Optional[dict] = None,
|
|
67
|
-
params: Optional[dict] = None,
|
|
68
|
-
**kwargs,
|
|
69
|
-
) -> Response:
|
|
70
|
-
response = self.base_request(
|
|
71
|
-
url=url,
|
|
72
|
-
method="GET",
|
|
73
|
-
auth=auth,
|
|
74
|
-
headers=headers,
|
|
75
|
-
timeout=request_timeout,
|
|
76
|
-
json_body=json_body,
|
|
77
|
-
data=data,
|
|
78
|
-
params=params,
|
|
79
|
-
**kwargs,
|
|
80
|
-
)
|
|
81
|
-
return response
|
|
82
|
-
|
|
83
|
-
def post(
|
|
84
|
-
self,
|
|
85
|
-
url: str,
|
|
86
|
-
json_body=None,
|
|
87
|
-
request_timeout: float = settings.REQUEST_TIMEOUT,
|
|
88
|
-
auth: Optional[tuple] = None,
|
|
89
|
-
headers: Optional[dict] = None,
|
|
90
|
-
data: Optional[dict] = None,
|
|
91
|
-
params: Optional[dict] = None,
|
|
92
|
-
**kwargs,
|
|
93
|
-
) -> Response:
|
|
94
|
-
response = self.base_request(
|
|
95
|
-
url=url,
|
|
96
|
-
method="POST",
|
|
97
|
-
auth=auth,
|
|
98
|
-
headers=headers,
|
|
99
|
-
timeout=request_timeout,
|
|
100
|
-
json_body=json_body,
|
|
101
|
-
data=data,
|
|
102
|
-
params=params,
|
|
103
|
-
**kwargs,
|
|
104
|
-
)
|
|
105
|
-
return response
|
|
106
|
-
|
|
107
|
-
def delete(
|
|
108
|
-
self,
|
|
109
|
-
url: str,
|
|
110
|
-
json_body=None,
|
|
111
|
-
request_timeout: float = settings.REQUEST_TIMEOUT,
|
|
112
|
-
auth: Optional[tuple] = None,
|
|
113
|
-
headers: Optional[dict] = None,
|
|
114
|
-
data: Optional[dict] = None,
|
|
115
|
-
params: Optional[dict] = None,
|
|
116
|
-
**kwargs,
|
|
117
|
-
) -> Response:
|
|
118
|
-
response = self.base_request(
|
|
119
|
-
url=url,
|
|
120
|
-
method="DELETE",
|
|
121
|
-
auth=auth,
|
|
122
|
-
headers=headers,
|
|
123
|
-
timeout=request_timeout,
|
|
124
|
-
json_body=json_body,
|
|
125
|
-
data=data,
|
|
126
|
-
params=params,
|
|
127
|
-
**kwargs,
|
|
128
|
-
)
|
|
129
|
-
return response
|
|
130
|
-
|
|
131
|
-
def put(
|
|
132
|
-
self,
|
|
133
|
-
url: str,
|
|
134
|
-
json_body=None,
|
|
135
|
-
request_timeout: float = settings.REQUEST_TIMEOUT,
|
|
136
|
-
auth: Optional[tuple] = None,
|
|
137
|
-
headers: Optional[dict] = None,
|
|
138
|
-
data: Optional[dict] = None,
|
|
139
|
-
params: Optional[dict] = None,
|
|
140
|
-
**kwargs,
|
|
141
|
-
) -> Response:
|
|
142
|
-
response = self.base_request(
|
|
143
|
-
url=url,
|
|
144
|
-
method="PUT",
|
|
145
|
-
auth=auth,
|
|
146
|
-
headers=headers,
|
|
147
|
-
timeout=request_timeout,
|
|
148
|
-
json_body=json_body,
|
|
149
|
-
data=data,
|
|
150
|
-
params=params,
|
|
151
|
-
**kwargs,
|
|
152
|
-
)
|
|
153
|
-
return response
|
|
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,68 +0,0 @@
|
|
|
1
|
-
from threading import Lock, Thread
|
|
2
|
-
from typing import Any, Optional
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class ThreadedRequest:
|
|
6
|
-
def __init__(self, request_func: Any):
|
|
7
|
-
self._thread: Optional[Thread] = None
|
|
8
|
-
self._request_func: Any = request_func
|
|
9
|
-
self._output: Optional[Any] = None
|
|
10
|
-
self._output_lock: Lock = Lock()
|
|
11
|
-
self._exception: Optional[Exception] = None
|
|
12
|
-
self._exception_lock: Lock = Lock()
|
|
13
|
-
|
|
14
|
-
def start_thread(self, *request_args, **kwargs) -> bool:
|
|
15
|
-
if self._is_thread_alive():
|
|
16
|
-
return False
|
|
17
|
-
self._output = None
|
|
18
|
-
self._thread = Thread(target=self._thread_func, args=request_args, **kwargs)
|
|
19
|
-
self._thread.start()
|
|
20
|
-
return True
|
|
21
|
-
|
|
22
|
-
def get_output(self) -> Any:
|
|
23
|
-
if self._is_thread_alive():
|
|
24
|
-
raise ThreadedRequestNotFinishedError
|
|
25
|
-
|
|
26
|
-
self._exception_lock.acquire()
|
|
27
|
-
exception = self._exception
|
|
28
|
-
self._exception_lock.release()
|
|
29
|
-
|
|
30
|
-
if exception:
|
|
31
|
-
raise exception
|
|
32
|
-
|
|
33
|
-
self._output_lock.acquire()
|
|
34
|
-
output = self._output
|
|
35
|
-
self._output_lock.release()
|
|
36
|
-
|
|
37
|
-
return output
|
|
38
|
-
|
|
39
|
-
def wait_for_thread(self) -> None:
|
|
40
|
-
if not self._thread:
|
|
41
|
-
return
|
|
42
|
-
self._thread.join()
|
|
43
|
-
|
|
44
|
-
def _is_thread_alive(self) -> bool:
|
|
45
|
-
if not self._thread:
|
|
46
|
-
return False
|
|
47
|
-
return self._thread.is_alive()
|
|
48
|
-
|
|
49
|
-
def _thread_func(self, *args) -> None:
|
|
50
|
-
try:
|
|
51
|
-
request_output: Any = self._request_func(*args)
|
|
52
|
-
except Exception as e:
|
|
53
|
-
self._exception_lock.acquire()
|
|
54
|
-
self._exception = e
|
|
55
|
-
self._exception_lock.release()
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
self._output_lock.acquire()
|
|
59
|
-
self._output = request_output
|
|
60
|
-
self._output_lock.release()
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class ThreadedRequestError(Exception):
|
|
64
|
-
pass
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class ThreadedRequestNotFinishedError(ThreadedRequestError):
|
|
68
|
-
pass
|
|
@@ -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()
|