diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 8c6fb908..b977e6ad 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.11", "3.12"] + python-version: ["3.9", "3.11", "3.12"] steps: - uses: actions/checkout@v4 @@ -24,11 +24,20 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + - name: Upgrade pip run: | python -m pip install --upgrade pip + + - name: Install dependencies with the compiled requirements (for 3.12) + if: ${{matrix.python-version == '3.12'}} + run: | pip install -r requirements.txt -e .[dev] + - name: Install dependencies based on the pyproject.toml file (for 3.9 and 3.11) + if: ${{matrix.python-version != '3.12'}} + run: | + pip install -e .[dev] + - name: Test with pytest run: | pytest diff --git a/pyproject.toml b/pyproject.toml index c917a985..d77aa3df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "isar" authors = [{ name = "Equinor ASA", email = "fg_robots_dev@equinor.com" }] description = "Integration and Supervisory control of Autonomous Robots" readme = "README.md" -requires-python = ">=3.11" +requires-python = ">=3.9" license = { file = "LICENSE" } classifiers = [ "Intended Audience :: Developers", @@ -15,6 +15,7 @@ classifiers = [ "License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0)", "Natural Language :: English", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", diff --git a/src/isar/apis/models/start_mission_definition.py b/src/isar/apis/models/start_mission_definition.py index d8fcc4be..6a74ba9e 100644 --- a/src/isar/apis/models/start_mission_definition.py +++ b/src/isar/apis/models/start_mission_definition.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Any, Dict, List, Optional, Union -from alitra import Position, Pose, Orientation, Frame +from alitra import Frame, Orientation, Pose, Position from pydantic import BaseModel, Field from isar.apis.models.models import InputPose, InputPosition @@ -132,20 +132,21 @@ def check_for_duplicate_ids(items: Union[List[Task], List[STEPS]]): def generate_steps(task) -> List[STEPS]: steps: List[STEPS] = [] - try: - match task.type: - case TaskType.Inspection: - steps.extend(generate_steps_for_inspection_task(task=task)) - case TaskType.DriveTo: - steps.append(generate_steps_for_drive_to_task(task=task)) - case TaskType.Localization: - steps.append(generate_steps_for_localization_task(task=task)) - case TaskType.ReturnToHome: - steps.append(generate_steps_for_return_to_home_task(task=task)) - case TaskType.Dock: - steps.append(generate_steps_for_dock_task()) - except ValueError as e: - raise MissionPlannerError(f"Failed to create task: {str(e)}") + + if task.type == TaskType.Inspection: + steps.extend(generate_steps_for_inspection_task(task=task)) + elif task.type == TaskType.DriveTo: + steps.append(generate_steps_for_drive_to_task(task=task)) + elif task.type == TaskType.Localization: + steps.append(generate_steps_for_localization_task(task=task)) + elif task.type == TaskType.ReturnToHome: + steps.append(generate_steps_for_return_to_home_task(task=task)) + elif task.type == TaskType.Dock: + steps.append(generate_steps_for_dock_task()) + else: + raise MissionPlannerError( + f"Failed to create task: '{task.type}' is not a valid" + ) return steps diff --git a/src/isar/services/service_connections/mqtt/robot_heartbeat_publisher.py b/src/isar/services/service_connections/mqtt/robot_heartbeat_publisher.py index 0c6828bd..db92100e 100644 --- a/src/isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +++ b/src/isar/services/service_connections/mqtt/robot_heartbeat_publisher.py @@ -1,6 +1,6 @@ import json import time -from datetime import UTC, datetime +from datetime import datetime, timezone from queue import Queue from isar.config.settings import settings @@ -18,7 +18,7 @@ def run(self) -> None: payload: RobotHeartbeatPayload = RobotHeartbeatPayload( isar_id=settings.ISAR_ID, robot_name=settings.ROBOT_NAME, - timestamp=datetime.now(UTC), + timestamp=datetime.now(timezone.utc), ) self.mqtt_publisher.publish( diff --git a/src/isar/services/service_connections/mqtt/robot_info_publisher.py b/src/isar/services/service_connections/mqtt/robot_info_publisher.py index e854e927..3afafb53 100644 --- a/src/isar/services/service_connections/mqtt/robot_info_publisher.py +++ b/src/isar/services/service_connections/mqtt/robot_info_publisher.py @@ -1,6 +1,6 @@ import json import time -from datetime import UTC, datetime +from datetime import datetime, timezone from queue import Queue from isar.config.settings import robot_settings, settings @@ -25,7 +25,7 @@ def run(self) -> None: host=settings.API_HOST_VIEWED_EXTERNALLY, port=settings.API_PORT, capabilities=robot_settings.CAPABILITIES, - timestamp=datetime.now(UTC), + timestamp=datetime.now(timezone.utc), ) self.mqtt_publisher.publish( diff --git a/src/isar/state_machine/state_machine.py b/src/isar/state_machine/state_machine.py index 2b951e65..4b7bd0f4 100644 --- a/src/isar/state_machine/state_machine.py +++ b/src/isar/state_machine/state_machine.py @@ -2,7 +2,7 @@ import logging import queue from collections import deque -from datetime import UTC, datetime +from datetime import datetime, timezone from typing import Deque, List, Optional from alitra import Pose @@ -526,7 +526,7 @@ def publish_mission_status(self) -> None: "error_description": ( error_message.error_description if error_message else None ), - "timestamp": datetime.now(UTC), + "timestamp": datetime.now(timezone.utc), }, cls=EnhancedJSONEncoder, ) @@ -559,7 +559,7 @@ def publish_task_status(self, task: Task) -> None: "error_description": ( error_message.error_description if error_message else None ), - "timestamp": datetime.now(UTC), + "timestamp": datetime.now(timezone.utc), }, cls=EnhancedJSONEncoder, ) @@ -594,7 +594,7 @@ def publish_step_status(self, step: Step) -> None: "error_description": ( error_message.error_description if error_message else None ), - "timestamp": datetime.now(UTC), + "timestamp": datetime.now(timezone.utc), }, cls=EnhancedJSONEncoder, ) @@ -614,7 +614,7 @@ def publish_status(self) -> None: "isar_id": settings.ISAR_ID, "robot_name": settings.ROBOT_NAME, "status": self._current_status(), - "timestamp": datetime.now(UTC), + "timestamp": datetime.now(timezone.utc), }, cls=EnhancedJSONEncoder, ) diff --git a/src/isar/storage/uploader.py b/src/isar/storage/uploader.py index d05c9248..7fccbf2b 100644 --- a/src/isar/storage/uploader.py +++ b/src/isar/storage/uploader.py @@ -1,7 +1,7 @@ import json import logging from dataclasses import dataclass -from datetime import UTC, datetime, timedelta +from datetime import datetime, timedelta, timezone from queue import Empty, Queue from typing import List, Union @@ -22,12 +22,12 @@ class UploaderQueueItem: mission: Mission storage_handler: StorageInterface _retry_count: int - _next_retry_time: datetime = datetime.now(UTC) + _next_retry_time: datetime = datetime.now(timezone.utc) def increment_retry(self, max_wait_time: int) -> None: self._retry_count += 1 seconds_until_retry: int = min(2**self._retry_count, max_wait_time) - self._next_retry_time = datetime.now(UTC) + timedelta( + self._next_retry_time = datetime.now(timezone.utc) + timedelta( seconds=seconds_until_retry ) @@ -35,10 +35,12 @@ def get_retry_count(self) -> int: return self._retry_count def is_ready_for_upload(self) -> bool: - return datetime.now(UTC) >= self._next_retry_time + return datetime.now(timezone.utc) >= self._next_retry_time def seconds_until_retry(self) -> int: - return max(0, int((self._next_retry_time - datetime.now(UTC)).total_seconds())) + return max( + 0, int((self._next_retry_time - datetime.now(timezone.utc)).total_seconds()) + ) class Uploader: @@ -154,7 +156,7 @@ def _publish_inspection_result( "inspection_id": inspection.id, "inspection_path": inspection_path, "analysis_type": inspection.metadata.analysis_type, - "timestamp": datetime.now(UTC), + "timestamp": datetime.now(timezone.utc), }, cls=EnhancedJSONEncoder, ) diff --git a/src/isar/storage/utilities.py b/src/isar/storage/utilities.py index a745cb00..1e060081 100644 --- a/src/isar/storage/utilities.py +++ b/src/isar/storage/utilities.py @@ -1,6 +1,6 @@ import json import time -from datetime import UTC, datetime +from datetime import datetime, timezone from pathlib import Path from typing import Tuple @@ -37,7 +37,7 @@ def construct_metadata_file( "mission_id": mission.id, "mission_name": mission.name, "plant_name": settings.PLANT_NAME, - "mission_date": datetime.now(UTC).date(), + "mission_date": datetime.now(timezone.utc).date(), "isar_id": settings.ISAR_ID, "robot_name": settings.ROBOT_NAME, "analysis_type": ( @@ -80,4 +80,4 @@ def get_filename( def get_foldername(mission: Mission) -> str: mission_name: str = mission.name.replace(" ", "-") - return f"{datetime.now(UTC).date()}__{settings.PLANT_SHORT_NAME}__{mission_name}__{mission.id}" + return f"{datetime.now(timezone.utc).date()}__{settings.PLANT_SHORT_NAME}__{mission_name}__{mission.id}" diff --git a/src/robot_interface/telemetry/mqtt_client.py b/src/robot_interface/telemetry/mqtt_client.py index edbdf13d..d3029e18 100644 --- a/src/robot_interface/telemetry/mqtt_client.py +++ b/src/robot_interface/telemetry/mqtt_client.py @@ -1,7 +1,7 @@ import json import time from abc import ABCMeta, abstractmethod -from datetime import UTC, datetime +from datetime import datetime, timezone from queue import Queue from typing import Callable, Tuple @@ -73,7 +73,7 @@ def run(self, isar_id: str, robot_name: str) -> None: topic = self.topic except RobotTelemetryException: payload = json.dumps( - CloudHealthPayload(isar_id, robot_name, datetime.now(UTC)), + CloudHealthPayload(isar_id, robot_name, datetime.now(timezone.utc)), cls=EnhancedJSONEncoder, ) topic = self.cloud_healt_topic diff --git a/tests/integration/turtlebot/test_successful_mission.py b/tests/integration/turtlebot/test_successful_mission.py index 1688a186..9159db30 100644 --- a/tests/integration/turtlebot/test_successful_mission.py +++ b/tests/integration/turtlebot/test_successful_mission.py @@ -1,7 +1,7 @@ import shutil import time from copy import deepcopy -from datetime import UTC, datetime, timedelta +from datetime import datetime, timedelta, timezone from pathlib import Path from typing import List @@ -102,9 +102,9 @@ def test_successful_mission( mission_id: str = state_machine_thread.state_machine.current_mission.id mission: Mission = deepcopy(state_machine_thread.state_machine.current_mission) - start_time: datetime = datetime.now(UTC) + start_time: datetime = datetime.now(timezone.utc) while state_machine_thread.state_machine.current_state != States.Idle: - if (datetime.now(UTC) - start_time) > integration_test_timeout: + if (datetime.now(timezone.utc) - start_time) > integration_test_timeout: raise TimeoutError time.sleep(5)