diff --git a/inorbit_edge/models.py b/inorbit_edge/models.py index cfad064..6752a6a 100644 --- a/inorbit_edge/models.py +++ b/inorbit_edge/models.py @@ -8,10 +8,13 @@ from typing import Optional # Third-party -from pydantic import BaseModel, AnyUrl, field_validator, HttpUrl +from pydantic import AnyUrl, BaseModel, HttpUrl, field_validator # InOrbit -from inorbit_edge.robot import INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL, INORBIT_REST_API_URL +from inorbit_edge.robot import ( + INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL, + INORBIT_DEFAULT_API_URL, +) class CameraConfig(BaseModel): @@ -85,10 +88,10 @@ class RobotSessionModel(BaseModel): * INORBIT_API_KEY (required): The InOrbit API key * INORBIT_USE_SSL: If SSL should be used (default is true) - * INORBIT_API_URL: The URL of the API (default is - inorbit_edge.robot.INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL) - * INORBIT_REST_API_URL: The URL of the InOrbit REST API (default is - inorbit_edge.robot.INORBIT_REST_API_URL) + * INORBIT_CONNECTION_CONFIG_URL: The URL of the MQTT configuration API + (default is inorbit_edge.robot.INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL) + * INORBIT_API_URL: The URL of the InOrbit REST API (default is + inorbit_edge.robot.INORBIT_DEFAULT_API_URL) Attributes: robot_id (str): The unique ID of the robot @@ -99,7 +102,7 @@ class RobotSessionModel(BaseModel): endpoint (HttpUrl, optional): The URL of the API or inorbit_edge's INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL by default rest_api_endpoint (HttpUrl, optional): The URL of the InOrbit REST API. - INORBIT_REST_API_URL by default. + INORBIT_DEFAULT_API_URL by default. """ robot_id: str @@ -108,10 +111,10 @@ class RobotSessionModel(BaseModel): api_key: Optional[str] = os.getenv("INORBIT_API_KEY") use_ssl: bool = os.environ.get("INORBIT_USE_SSL", "true").lower() == "true" endpoint: HttpUrl = os.environ.get( - "INORBIT_API_URL", INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL + "INORBIT_CONNECTION_CONFIG_URL", INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL ) rest_api_endpoint: Optional[HttpUrl] = os.environ.get( - "INORBIT_REST_API_URL", INORBIT_REST_API_URL + "INORBIT_API_URL", INORBIT_DEFAULT_API_URL ) # noinspection PyMethodParameters diff --git a/inorbit_edge/robot.py b/inorbit_edge/robot.py index f9104a8..1815f53 100644 --- a/inorbit_edge/robot.py +++ b/inorbit_edge/robot.py @@ -1,76 +1,76 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- import io -import zlib -from dataclasses import dataclass, field, asdict import json -from typing import Tuple, Optional, List, Dict - -from inorbit_edge import __version__ as inorbit_edge_version -from inorbit_edge.types import Pose, SpatialTolerance -from inorbit_edge.metrics import ( - attrs_from_self, - with_counter_metric, - publish_map_counter, - publish_camera_frame_counter, - publish_pose_counter, - publish_key_values_counter, - publish_system_stats_counter, - publish_odometry_counter, - publish_laser_counter, - publish_path_counter, -) -import os import logging -import paho.mqtt.client as mqtt -from PIL import Image -from urllib.parse import urlsplit -import socks +import math +import os +import re import ssl +import subprocess import threading +import time +import zlib +from dataclasses import asdict, dataclass, field +from typing import Dict, List, Optional, Tuple +from urllib.parse import urlsplit + +import certifi +import paho.mqtt.client as mqtt +import requests +import socks import yaml +from deprecated import deprecated +from PIL import Image +from inorbit_edge import __version__ as inorbit_edge_version +from inorbit_edge.commands import ( + COMMAND_CUSTOM_COMMAND, + COMMAND_INITIAL_POSE, + COMMAND_MESSAGE, + COMMAND_NAV_GOAL, +) from inorbit_edge.inorbit_pb2 import ( + CameraMessage, + CustomCommandRosMessage, CustomDataMessage, + CustomScriptCommandMessage, + CustomScriptStatusMessage, + Echo, KeyValueCustomElement, + LaserMessage, LocationAndPoseMessage, + MapMessage, + MapRequest, OdometryDataMessage, - LaserMessage, + PathDataMessage, PathPoint, RobotPath, - PathDataMessage, - Echo, - CustomScriptCommandMessage, - CustomScriptStatusMessage, - CustomCommandRosMessage, - CameraMessage, SystemStatsMessage, - MapMessage, - MapRequest, ) -from inorbit_edge.video import CameraStreamer, Camera -from inorbit_edge.missions import MissionsModule -from inorbit_edge.commands import ( - COMMAND_INITIAL_POSE, - COMMAND_NAV_GOAL, - COMMAND_CUSTOM_COMMAND, - COMMAND_MESSAGE, +from inorbit_edge.metrics import ( + attrs_from_self, + publish_camera_frame_counter, + publish_key_values_counter, + publish_laser_counter, + publish_map_counter, + publish_odometry_counter, + publish_path_counter, + publish_pose_counter, + publish_system_stats_counter, + with_counter_metric, ) -import time -import requests -import math +from inorbit_edge.missions import MissionsModule +from inorbit_edge.types import Pose, SpatialTolerance from inorbit_edge.utils import ( + calculate_pose_delta, encode_floating_point_list, reduce_path, - calculate_pose_delta, ) -import certifi -import subprocess -import re -from deprecated import deprecated +from inorbit_edge.video import Camera, CameraStreamer INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL = "https://control.inorbit.ai/cloud_sdk_robot_config" -INORBIT_REST_API_URL = "https://api.inorbit.ai" +INORBIT_DEFAULT_API_URL = "https://api.inorbit.ai" MQTT_SUBTOPIC_POSE = "ros/loc/data2" MQTT_SUBTOPIC_PATH = "ros/loc/path" @@ -299,7 +299,6 @@ def discard_next_delta(self): class RobotSession: - def __init__(self, robot_id, robot_name, api_key=None, **kwargs) -> None: """Initialize a robot session. @@ -315,7 +314,7 @@ def __init__(self, robot_id, robot_name, api_key=None, **kwargs) -> None: endpoint (str): InOrbit URL. Defaults: INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL. use_ssl (bool): Configures MQTT client to use SSL. Defaults: True. rest_api_endpoint (str): The URL of the InOrbit REST API. - Defaults: INORBIT_REST_API_URL. + Defaults: INORBIT_DEFAULT_API_URL. keepalive_secs (int): Keepalive for MQTT connection (seconds). Default: 10. estimate_distance_linear (bool): Whether to publish an estimate value for linear_distance based on poses when the value is not provided on a @@ -348,7 +347,7 @@ def __init__(self, robot_id, robot_name, api_key=None, **kwargs) -> None: self.use_ssl = kwargs.get("use_ssl", True) # InOrbit REST API endpoint self.inorbit_rest_api_endpoint = kwargs.get( - "rest_api_endpoint", INORBIT_REST_API_URL + "rest_api_endpoint", INORBIT_DEFAULT_API_URL ) # Account ID, lazily fetched from REST API via get_account_id() self._account_id: str | None = None @@ -1669,7 +1668,7 @@ def get_account_id(self) -> str: account_ids = response.json().get("accountIds") or [] if not isinstance(account_ids, list): raise ValueError( - "Unexpected response from the REST API: " "accountIds is not a list" + "Unexpected response from the REST API: accountIds is not a list" ) if len(account_ids) == 0: raise ValueError("No account IDs found for the authenticated API key") diff --git a/inorbit_edge/tests/demo/README.md b/inorbit_edge/tests/demo/README.md index 3f85916..62483be 100644 --- a/inorbit_edge/tests/demo/README.md +++ b/inorbit_edge/tests/demo/README.md @@ -14,7 +14,7 @@ cd /path/to/edge-sdk-python pip install -e '.[video,telemetry]' cd inorbit_edge/tests/demo -export INORBIT_URL="https://control.inorbit.ai" +export INORBIT_CONNECTION_CONFIG_URL="https://control.inorbit.ai" export INORBIT_API_URL="https://api.inorbit.ai" export INORBIT_API_KEY="foobar123" export INORBIT_ROBOT_ID_PREFIX="$(hostname)" @@ -51,7 +51,7 @@ metrics on the host: ```bash docker run --rm -p 9464:9464 \ -v "$PWD/inorbit_edge/tests/demo:/demo:ro" \ - -e INORBIT_URL=... \ + -e INORBIT_CONNECTION_CONFIG_URL=... \ -e INORBIT_API_URL=... \ -e INORBIT_API_KEY=... \ -e INORBIT_ROBOT_ID_PREFIX=$(hostname) \ diff --git a/inorbit_edge/tests/demo/example.py b/inorbit_edge/tests/demo/example.py index 6f3ba9e..1ce9d45 100644 --- a/inorbit_edge/tests/demo/example.py +++ b/inorbit_edge/tests/demo/example.py @@ -206,7 +206,7 @@ def main(): radius=0.2, ) - inorbit_api_endpoint = _required_env("INORBIT_URL") + inorbit_api_endpoint = _required_env("INORBIT_CONNECTION_CONFIG_URL") inorbit_api_url = _required_env("INORBIT_API_URL") inorbit_api_key = _required_env("INORBIT_API_KEY") diff --git a/inorbit_edge/tests/test_robot_session.py b/inorbit_edge/tests/test_robot_session.py index 4412557..71d3c7e 100644 --- a/inorbit_edge/tests/test_robot_session.py +++ b/inorbit_edge/tests/test_robot_session.py @@ -4,13 +4,19 @@ import math import os from unittest.mock import MagicMock + import pytest from requests import HTTPError -from inorbit_edge.robot import RobotSession, RobotFootprintSpec, RobotMap -from inorbit_edge.robot import INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL, INORBIT_REST_API_URL from inorbit_edge import get_module_version -from inorbit_edge.inorbit_pb2 import MapMessage, RobotPath, PathDataMessage, PathPoint +from inorbit_edge.inorbit_pb2 import MapMessage, PathDataMessage, PathPoint, RobotPath +from inorbit_edge.robot import ( + INORBIT_CLOUD_SDK_ROBOT_CONFIG_URL, + INORBIT_DEFAULT_API_URL, + RobotFootprintSpec, + RobotMap, + RobotSession, +) def test_robot_session_init(monkeypatch, mock_sleep): @@ -125,11 +131,11 @@ def test_method_throttling(mock_sleep): def test_apply_footprint(requests_mock, mock_sleep): requests_mock.get( - f"{INORBIT_REST_API_URL}/user", + f"{INORBIT_DEFAULT_API_URL}/user", json={"userId": "user_abc", "name": "Test User", "accountIds": ["account_123"]}, ) adapter = requests_mock.post( - f"{INORBIT_REST_API_URL}/configuration/apply", + f"{INORBIT_DEFAULT_API_URL}/configuration/apply", json={"operationStatus": "SUCCESS"}, ) footprint = RobotFootprintSpec( @@ -168,14 +174,16 @@ def test_apply_footprint(requests_mock, mock_sleep): } # HTTP error on apply - requests_mock.post(f"{INORBIT_REST_API_URL}/configuration/apply", status_code=400) + requests_mock.post( + f"{INORBIT_DEFAULT_API_URL}/configuration/apply", status_code=400 + ) with pytest.raises(HTTPError): robot_session.apply_footprint(footprint) def test_get_account_id(requests_mock, mock_sleep): requests_mock.get( - f"{INORBIT_REST_API_URL}/user", + f"{INORBIT_DEFAULT_API_URL}/user", json={"userId": "user_abc", "name": "Test User", "accountIds": ["account_123"]}, ) robot_session = RobotSession( @@ -202,7 +210,7 @@ def test_get_account_id_no_api_key(mock_sleep): def test_get_account_id_empty_accounts(requests_mock, mock_sleep): requests_mock.get( - f"{INORBIT_REST_API_URL}/user", + f"{INORBIT_DEFAULT_API_URL}/user", json={"userId": "user_abc", "name": "Test User", "accountIds": []}, ) robot_session = RobotSession( @@ -216,7 +224,7 @@ def test_get_account_id_empty_accounts(requests_mock, mock_sleep): def test_get_account_id_multiple_accounts(requests_mock, mock_sleep): requests_mock.get( - f"{INORBIT_REST_API_URL}/user", + f"{INORBIT_DEFAULT_API_URL}/user", json={"userId": "user_abc", "name": "Test", "accountIds": ["a1", "a2"]}, ) robot_session = RobotSession( @@ -229,7 +237,7 @@ def test_get_account_id_multiple_accounts(requests_mock, mock_sleep): def test_get_account_id_api_error(requests_mock, mock_sleep): - requests_mock.get(f"{INORBIT_REST_API_URL}/user", status_code=401) + requests_mock.get(f"{INORBIT_DEFAULT_API_URL}/user", status_code=401) robot_session = RobotSession( robot_id="id_123", robot_name="name_123",