diff --git a/examples/server/v1/components.py b/examples/server/v1/components.py index 4f2f71c43..fe8e72bca 100644 --- a/examples/server/v1/components.py +++ b/examples/server/v1/components.py @@ -39,19 +39,27 @@ from viam.proto.common import ( AnalogStatus, BoardStatus, + Capsule, DigitalInterruptStatus, + Geometry, GeoPoint, KinematicsFileFormat, Orientation, Pose, PoseInFrame, - Vector3, ResponseMetadata, + Sphere, + Vector3, ) from viam.proto.component.arm import JointPositions from viam.proto.component.audioinput import AudioChunk, AudioChunkInfo, SampleFormat from viam.proto.component.encoder import PositionType +GEOMETRIES = [ + Geometry(center=Pose(x=1, y=2, z=3, o_x=2, o_y=3, o_z=4, theta=20), sphere=Sphere(radius_mm=2)), + Geometry(center=Pose(x=1, y=2, z=3, o_x=2, o_y=3, o_z=4, theta=20), capsule=Capsule(radius_mm=3, length_mm=8)), +] + class ExampleArm(Arm): def __init__(self, name: str): @@ -97,6 +105,9 @@ async def is_moving(self): async def get_kinematics(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[KinematicsFileFormat.ValueType, bytes]: return self.kinematics + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleAudioInput(AudioInput): def __init__(self, name: str): @@ -160,6 +171,9 @@ async def get_properties(self) -> AudioInput.Properties: is_interleaved=True, ) + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleBase(Base): def __init__(self, name: str): @@ -212,6 +226,9 @@ async def is_moving(self): async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> Base.Properties: return self.props + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleAnalogReader(Board.AnalogReader): def __init__(self, name: str, value: int): @@ -321,6 +338,9 @@ async def model_attributes(self) -> Board.Attributes: async def set_power_mode(self, **kwargs): raise NotImplementedError() + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleCamera(Camera): def __init__(self, name: str): @@ -343,6 +363,9 @@ async def get_point_cloud(self, **kwargs) -> Tuple[bytes, str]: async def get_properties(self, **kwargs) -> Camera.Properties: raise NotImplementedError() + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleController(Controller): CONTROL_MAP: Dict[int, Control] = { @@ -448,6 +471,9 @@ def register_control_callback(self, control: Control, triggers: List[EventType], callbacks[trigger] = function self.callbacks[control] = callbacks + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleEncoder(Encoder): def __init__(self, name: str): @@ -468,6 +494,9 @@ async def get_position( async def get_properties(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> Encoder.Properties: return Encoder.Properties(ticks_count_supported=True, angle_degrees_supported=False) + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleGantry(Gantry): def __init__(self, name: str, position: List[float], lengths: List[float]): @@ -501,6 +530,9 @@ async def stop(self, extra: Optional[Dict[str, Any]] = None, **kwargs): async def is_moving(self): return not self.is_stopped + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleGripper(Gripper): def __init__(self, name: str): @@ -523,6 +555,9 @@ async def stop(self, extra: Optional[Dict[str, Any]] = None, **kwargs): async def is_moving(self): return not self.is_stopped + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleMotor(Motor): def __init__(self, name: str): @@ -596,6 +631,9 @@ async def is_powered(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> async def is_moving(self): return self.powered + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleMovementSensor(MovementSensor): def __init__( @@ -646,6 +684,9 @@ async def get_properties(self, **kwargs) -> MovementSensor.Properties: async def get_accuracy(self, **kwargs) -> Mapping[str, float]: return self.accuracy + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExamplePoseTracker(PoseTracker): async def get_poses(self, body_names: List[str], **kwargs) -> Dict[str, PoseInFrame]: @@ -655,6 +696,9 @@ async def get_poses(self, body_names: List[str], **kwargs) -> Dict[str, PoseInFr } return {k: v for k, v in all_poses.items() if k in body_names} + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleSensor(Sensor): def __init__(self, name: str): @@ -664,6 +708,9 @@ def __init__(self, name: str): async def get_readings(self, **kwargs) -> Mapping[str, Any]: return {"abcdefghij"[idx]: random.random() for idx in range(self.num_readings)} + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES + class ExampleServo(Servo): def __init__(self, name: str): @@ -683,3 +730,6 @@ async def stop(self, **kwargs): async def is_moving(self): return not self.is_stopped + + async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> List[Geometry]: + return GEOMETRIES diff --git a/src/viam/components/arm/__init__.py b/src/viam/components/arm/__init__.py index 8e38680fa..bf9a7d765 100644 --- a/src/viam/components/arm/__init__.py +++ b/src/viam/components/arm/__init__.py @@ -1,6 +1,6 @@ import asyncio -from viam.proto.common import KinematicsFileFormat, Pose +from viam.proto.common import Geometry, KinematicsFileFormat, Pose from viam.proto.component.arm import JointPositions from viam.proto.component.arm import Status as ArmStatus from viam.proto.robot import Status @@ -13,6 +13,7 @@ __all__ = [ "Arm", + "Geometry", "JointPositions", "KinematicsFileFormat", "Pose", @@ -20,11 +21,7 @@ async def create_status(component: Arm) -> Status: - ( - end_position, - joint_positions, - is_moving, - ) = await asyncio.gather( + (end_position, joint_positions, is_moving,) = await asyncio.gather( component.get_end_position(), component.get_joint_positions(), component.is_moving(), diff --git a/src/viam/components/arm/arm.py b/src/viam/components/arm/arm.py index 44c28f9c2..1d72a7273 100644 --- a/src/viam/components/arm/arm.py +++ b/src/viam/components/arm/arm.py @@ -1,10 +1,10 @@ import abc -from typing import Any, Dict, Final, Optional, Tuple +from typing import Any, Dict, Final, List, Optional, Tuple from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase -from . import JointPositions, KinematicsFileFormat, Pose +from . import Geometry, JointPositions, KinematicsFileFormat, Pose class Arm(ComponentBase): @@ -122,3 +122,13 @@ async def get_kinematics( - bytes: The byte contents of the file. """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the arm, in their current configuration, in the frame of the arm. + + Returns: + List[Geometry]: The geometries associated with the arm. + """ + ... diff --git a/src/viam/components/arm/client.py b/src/viam/components/arm/client.py index 5fc82e3fa..0b4022e7c 100644 --- a/src/viam/components/arm/client.py +++ b/src/viam/components/arm/client.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Mapping, Optional, Tuple +from typing import Any, Dict, List, Mapping, Optional, Tuple from grpclib.client import Channel @@ -17,9 +17,9 @@ StopRequest, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict -from . import Arm, KinematicsFileFormat, Pose +from . import Arm, Geometry, KinematicsFileFormat, Pose class ArmClient(Arm, ReconfigurableResourceRPCClientBase): @@ -116,3 +116,6 @@ async def get_kinematics( request = GetKinematicsRequest(name=self.name, extra=dict_to_struct(extra)) response: GetKinematicsResponse = await self.client.GetKinematics(request, timeout=timeout) return (response.format, response.kinematics_data) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/arm/service.py b/src/viam/components/arm/service.py index 0bea8fb45..c061169ef 100644 --- a/src/viam/components/arm/service.py +++ b/src/viam/components/arm/service.py @@ -1,6 +1,5 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.proto.common import ( DoCommandRequest, DoCommandResponse, @@ -115,4 +114,10 @@ async def GetKinematics(self, stream: Stream[GetKinematicsRequest, GetKinematics await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + arm = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await arm.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/audio_input/__init__.py b/src/viam/components/audio_input/__init__.py index 457fa34c9..2de28a3b8 100644 --- a/src/viam/components/audio_input/__init__.py +++ b/src/viam/components/audio_input/__init__.py @@ -1,3 +1,4 @@ +from viam.proto.common import Geometry from viam.resource.registry import Registry, ResourceRegistration from .audio_input import AudioInput @@ -6,6 +7,7 @@ __all__ = [ "AudioInput", + "Geometry", ] diff --git a/src/viam/components/audio_input/audio_input.py b/src/viam/components/audio_input/audio_input.py index 6908a5a7f..c79fd5948 100644 --- a/src/viam/components/audio_input/audio_input.py +++ b/src/viam/components/audio_input/audio_input.py @@ -1,7 +1,7 @@ import abc from dataclasses import dataclass from datetime import timedelta -from typing import Final, Optional +from typing import Final, List, Optional from google.protobuf.duration_pb2 import Duration from typing_extensions import Self @@ -12,6 +12,7 @@ from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase +from . import Geometry class AudioInput(ComponentBase, MediaSource[Audio]): @@ -77,3 +78,13 @@ async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> Properties: The audio input properties """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the audio input, in their current configuration, in the frame of the audio input. + + Returns: + List[Geometry]: The geometries associated with the audio input. + """ + ... diff --git a/src/viam/components/audio_input/client.py b/src/viam/components/audio_input/client.py index 80ad059c4..6706f7064 100644 --- a/src/viam/components/audio_input/client.py +++ b/src/viam/components/audio_input/client.py @@ -1,4 +1,4 @@ -from typing import AsyncIterator, Mapping, Optional, Union +from typing import Any, AsyncIterator, Dict, List, Mapping, Optional, Union from grpclib.client import Channel @@ -14,9 +14,9 @@ SampleFormat, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict -from .audio_input import AudioInput +from .audio_input import AudioInput, Geometry class AudioInputClient(AudioInput, ReconfigurableResourceRPCClientBase): @@ -58,3 +58,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/audio_input/service.py b/src/viam/components/audio_input/service.py index 7838a75ff..5f0fab4f6 100644 --- a/src/viam/components/audio_input/service.py +++ b/src/viam/components/audio_input/service.py @@ -6,7 +6,7 @@ from grpclib import GRPCError, Status from grpclib.server import Stream -from viam.errors import MethodNotImplementedError, NotSupportedError +from viam.errors import NotSupportedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.audioinput import ( AudioInputServiceBase, @@ -105,4 +105,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + audio_input = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await audio_input.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/base/__init__.py b/src/viam/components/base/__init__.py index da87de085..e847676e4 100644 --- a/src/viam/components/base/__init__.py +++ b/src/viam/components/base/__init__.py @@ -1,4 +1,4 @@ -from viam.proto.common import ActuatorStatus, Vector3 +from viam.proto.common import ActuatorStatus, Geometry, Vector3 from viam.proto.robot import Status from viam.resource.registry import Registry, ResourceRegistration from viam.utils import message_to_struct @@ -7,7 +7,11 @@ from .client import BaseClient from .service import BaseRPCService -__all__ = ["Base", "Vector3"] +__all__ = [ + "Base", + "Geometry", + "Vector3", +] async def create_status(component: Base) -> Status: diff --git a/src/viam/components/base/base.py b/src/viam/components/base/base.py index 2189c653f..6662c2f35 100644 --- a/src/viam/components/base/base.py +++ b/src/viam/components/base/base.py @@ -1,11 +1,11 @@ import abc from dataclasses import dataclass -from typing import Any, Dict, Final, Optional +from typing import Any, Dict, Final, List, Optional from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase -from . import Vector3 +from . import Geometry, Vector3 class Base(ComponentBase): @@ -148,3 +148,13 @@ async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> Properties: The properties of the base """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the base, in their current configuration, in the frame of the base. + + Returns: + List[Geometry]: The geometries associated with the base. + """ + ... diff --git a/src/viam/components/base/client.py b/src/viam/components/base/client.py index 32771d4cd..4e72c7e5e 100644 --- a/src/viam/components/base/client.py +++ b/src/viam/components/base/client.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Mapping, Optional +from typing import Any, Dict, List, Mapping, Optional from grpclib.client import Channel @@ -16,9 +16,9 @@ StopRequest, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict -from . import Base, Vector3 +from . import Base, Geometry, Vector3 class BaseClient(Base, ReconfigurableResourceRPCClientBase): @@ -130,3 +130,6 @@ async def do_command( request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/base/service.py b/src/viam/components/base/service.py index daf40362d..7b4ac06dc 100644 --- a/src/viam/components/base/service.py +++ b/src/viam/components/base/service.py @@ -1,6 +1,5 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.base import ( BaseServiceBase, @@ -126,4 +125,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + base = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await base.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/board/__init__.py b/src/viam/components/board/__init__.py index 3094c870a..57ee54551 100644 --- a/src/viam/components/board/__init__.py +++ b/src/viam/components/board/__init__.py @@ -1,3 +1,4 @@ +from viam.proto.common import Geometry from viam.proto.robot import Status from viam.resource.registry import Registry, ResourceRegistration from viam.utils import message_to_struct @@ -8,6 +9,7 @@ __all__ = [ "Board", + "Geometry", ] diff --git a/src/viam/components/board/board.py b/src/viam/components/board/board.py index 05fb792a0..cdd5e90c8 100644 --- a/src/viam/components/board/board.py +++ b/src/viam/components/board/board.py @@ -9,6 +9,7 @@ from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase +from . import Geometry PostProcessor = Callable[[int], int] @@ -262,3 +263,13 @@ async def set_power_mode( Args: mode: the desired power mode """ + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the board, in their current configuration, in the frame of the board. + + Returns: + List[Geometry]: The geometries associated with the board. + """ + ... diff --git a/src/viam/components/board/client.py b/src/viam/components/board/client.py index eb677e9a6..8522632e0 100644 --- a/src/viam/components/board/client.py +++ b/src/viam/components/board/client.py @@ -27,9 +27,10 @@ StatusResponse, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict -from .board import Board, PostProcessor +from .board import PostProcessor +from . import Board, Geometry class AnalogReaderClient(Board.AnalogReader): @@ -174,3 +175,6 @@ async def set_power_mode( duration_pb = [(d, d.FromTimedelta(duration)) for d in [Duration()]][0][0] request = SetPowerModeRequest(name=self.name, power_mode=mode, duration=duration_pb) await self.client.SetPowerMode(request, timeout=timeout) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/board/service.py b/src/viam/components/board/service.py index 42a9e6b57..5cf409b20 100644 --- a/src/viam/components/board/service.py +++ b/src/viam/components/board/service.py @@ -1,6 +1,6 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError, ResourceNotFoundError +from viam.errors import ResourceNotFoundError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.board import ( BoardServiceBase, @@ -185,4 +185,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + arm = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await arm.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/camera/__init__.py b/src/viam/components/camera/__init__.py index 6e3e2f736..a3147c2ff 100644 --- a/src/viam/components/camera/__init__.py +++ b/src/viam/components/camera/__init__.py @@ -1,4 +1,5 @@ from viam.media.video import RawImage +from viam.proto.common import Geometry from viam.proto.component.camera import DistortionParameters, IntrinsicParameters from viam.resource.registry import Registry, ResourceRegistration @@ -8,6 +9,7 @@ __all__ = [ "Camera", + "Geometry", "IntrinsicParameters", "DistortionParameters", "RawImage", diff --git a/src/viam/components/camera/camera.py b/src/viam/components/camera/camera.py index 23f0e6ee3..42c0dfdfb 100644 --- a/src/viam/components/camera/camera.py +++ b/src/viam/components/camera/camera.py @@ -8,7 +8,7 @@ from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase -from . import DistortionParameters, IntrinsicParameters, RawImage +from . import DistortionParameters, Geometry, IntrinsicParameters, RawImage class Camera(ComponentBase): @@ -103,3 +103,13 @@ async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> Properties: The properties of the camera """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the camera, in their current configuration, in the frame of the camera. + + Returns: + List[Geometry]: The geometries associated with the camera. + """ + ... diff --git a/src/viam/components/camera/client.py b/src/viam/components/camera/client.py index c3c76942b..9c8b74dc1 100644 --- a/src/viam/components/camera/client.py +++ b/src/viam/components/camera/client.py @@ -1,5 +1,5 @@ from io import BytesIO -from typing import List, Mapping, Optional, Tuple, Union +from typing import Any, Dict, List, Mapping, Optional, Tuple, Union from grpclib.client import Channel from PIL import Image @@ -18,9 +18,9 @@ GetPropertiesResponse, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict -from . import Camera, RawImage +from . import Camera, Geometry, RawImage def get_image_from_response(data: bytes, response_mime_type: str, request_mime_type: Optional[str] = None) -> Union[Image.Image, RawImage]: @@ -72,3 +72,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/camera/service.py b/src/viam/components/camera/service.py index d4b0a75ed..6bed86d31 100644 --- a/src/viam/components/camera/service.py +++ b/src/viam/components/camera/service.py @@ -3,7 +3,6 @@ from google.api.httpbody_pb2 import HttpBody from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.media.video import CameraMimeType from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.camera import ( @@ -135,4 +134,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + camera = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await camera.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/component_base.py b/src/viam/components/component_base.py index c052f71f9..ddc5d9fec 100644 --- a/src/viam/components/component_base.py +++ b/src/viam/components/component_base.py @@ -3,6 +3,7 @@ from typing_extensions import Self +from viam.proto.common import Geometry from viam.resource.base import ResourceBase if TYPE_CHECKING: @@ -40,3 +41,6 @@ def from_robot(cls, robot: "RobotClient", name: str) -> Self: async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: raise NotImplementedError() + + async def get_geometries(self) -> List[Geometry]: + raise NotImplementedError() diff --git a/src/viam/components/encoder/__init__.py b/src/viam/components/encoder/__init__.py index c885d409b..c67563b27 100644 --- a/src/viam/components/encoder/__init__.py +++ b/src/viam/components/encoder/__init__.py @@ -1,3 +1,4 @@ +from viam.proto.common import Geometry from viam.resource.registry import Registry, ResourceRegistration from .client import EncoderClient @@ -6,6 +7,7 @@ __all__ = [ "Encoder", + "Geometry", ] diff --git a/src/viam/components/encoder/client.py b/src/viam/components/encoder/client.py index 59be7c86a..dd6d07e43 100644 --- a/src/viam/components/encoder/client.py +++ b/src/viam/components/encoder/client.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Mapping, Optional, Tuple +from typing import Any, Dict, List, Mapping, Optional, Tuple from grpclib.client import Channel @@ -13,9 +13,10 @@ ResetPositionRequest, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict from .encoder import Encoder +from . import Geometry class EncoderClient(Encoder, ReconfigurableResourceRPCClientBase): @@ -70,3 +71,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/encoder/encoder.py b/src/viam/components/encoder/encoder.py index 29ecee9ca..209525469 100644 --- a/src/viam/components/encoder/encoder.py +++ b/src/viam/components/encoder/encoder.py @@ -1,11 +1,12 @@ import abc from dataclasses import dataclass -from typing import Any, Dict, Final, Optional, Tuple +from typing import Any, Dict, Final, List, Optional, Tuple from viam.proto.component.encoder import PositionType from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase +from . import Geometry class Encoder(ComponentBase): @@ -77,3 +78,13 @@ async def get_properties( Encoder.Properties: Map of position types to supported status. """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the encoder, in their current configuration, in the frame of the encoder. + + Returns: + List[Geometry]: The geometries associated with the encoder. + """ + ... diff --git a/src/viam/components/encoder/service.py b/src/viam/components/encoder/service.py index b5e654bda..2a6de4ddc 100644 --- a/src/viam/components/encoder/service.py +++ b/src/viam/components/encoder/service.py @@ -1,6 +1,5 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.encoder import ( EncoderServiceBase, @@ -64,4 +63,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + arm = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await arm.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/gantry/__init__.py b/src/viam/components/gantry/__init__.py index 36453d20d..442f9d9d5 100644 --- a/src/viam/components/gantry/__init__.py +++ b/src/viam/components/gantry/__init__.py @@ -1,5 +1,6 @@ import asyncio +from viam.proto.common import Geometry from viam.proto.component.gantry import Status as GantryStatus from viam.proto.robot import Status from viam.resource.registry import Registry, ResourceRegistration @@ -11,6 +12,7 @@ __all__ = [ "Gantry", + "Geometry", ] diff --git a/src/viam/components/gantry/client.py b/src/viam/components/gantry/client.py index 4104cfa8a..939efb30b 100644 --- a/src/viam/components/gantry/client.py +++ b/src/viam/components/gantry/client.py @@ -17,9 +17,10 @@ StopRequest, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict from .gantry import Gantry +from . import Geometry class GantryClient(Gantry, ReconfigurableResourceRPCClientBase): @@ -81,3 +82,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/gantry/gantry.py b/src/viam/components/gantry/gantry.py index 2f990c991..1094a6169 100644 --- a/src/viam/components/gantry/gantry.py +++ b/src/viam/components/gantry/gantry.py @@ -4,6 +4,7 @@ from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase +from . import Geometry class Gantry(ComponentBase): @@ -81,3 +82,13 @@ async def is_moving(self) -> bool: bool: Whether the gantry is moving. """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the gantry, in their current configuration, in the frame of the gantry. + + Returns: + List[Geometry]: The geometries associated with the gantry. + """ + ... diff --git a/src/viam/components/gantry/service.py b/src/viam/components/gantry/service.py index 22de40173..c4a189b56 100644 --- a/src/viam/components/gantry/service.py +++ b/src/viam/components/gantry/service.py @@ -1,6 +1,5 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.gantry import ( GantryServiceBase, @@ -105,4 +104,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + arm = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await arm.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/generic/__init__.py b/src/viam/components/generic/__init__.py index a199d1af7..6abc4a402 100644 --- a/src/viam/components/generic/__init__.py +++ b/src/viam/components/generic/__init__.py @@ -1,4 +1,5 @@ import viam.gen.component.generic.v1.generic_pb2 # Need this import for Generic service descriptors to resolve +from viam.proto.common import Geometry from viam.resource.registry import Registry, ResourceRegistration from .client import GenericClient @@ -7,6 +8,7 @@ __all__ = [ "Generic", + "Geometry", ] Registry.register_subtype( diff --git a/src/viam/components/generic/client.py b/src/viam/components/generic/client.py index e227bda5d..104db4043 100644 --- a/src/viam/components/generic/client.py +++ b/src/viam/components/generic/client.py @@ -1,4 +1,4 @@ -from typing import Any, Mapping, Optional +from typing import Any, Dict, List, Mapping, Optional from grpclib import GRPCError, Status from grpclib.client import Channel @@ -6,9 +6,10 @@ from viam.proto.common import DoCommandRequest, DoCommandResponse from viam.proto.component.generic import GenericServiceStub from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict from .generic import Generic +from . import Geometry class GenericClient(Generic, ReconfigurableResourceRPCClientBase): @@ -31,6 +32,9 @@ async def do_command(self, command: Mapping[str, Any], *, timeout: Optional[floa return struct_to_dict(response.result) + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) + async def do_command( channel: Channel, name: str, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None diff --git a/src/viam/components/generic/generic.py b/src/viam/components/generic/generic.py index eabc2331d..4dca7da31 100644 --- a/src/viam/components/generic/generic.py +++ b/src/viam/components/generic/generic.py @@ -1,8 +1,10 @@ -from typing import Final +import abc +from typing import Final, List from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase +from . import Geometry class Generic(ComponentBase): @@ -66,3 +68,13 @@ def complex_command(self, arg1, arg2, arg3): """ SUBTYPE: Final = Subtype(RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, "generic") + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the generic component, in their current configuration, in the frame of the generic component. + + Returns: + List[Geometry]: The geometries associated with the generic component. + """ + ... diff --git a/src/viam/components/generic/service.py b/src/viam/components/generic/service.py index 77b5b1e96..e8c60ea59 100644 --- a/src/viam/components/generic/service.py +++ b/src/viam/components/generic/service.py @@ -2,7 +2,6 @@ from grpclib.server import Stream from viam.components.component_base import ComponentBase -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.generic import GenericServiceBase from viam.resource.rpc_service_base import ResourceRPCServiceBase @@ -32,4 +31,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + component = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await component.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/gripper/__init__.py b/src/viam/components/gripper/__init__.py index 7b8cd9df4..ab58d004d 100644 --- a/src/viam/components/gripper/__init__.py +++ b/src/viam/components/gripper/__init__.py @@ -1,5 +1,4 @@ -from viam.components.gantry.gantry import Gantry -from viam.proto.common import ActuatorStatus +from viam.proto.common import ActuatorStatus, Geometry from viam.proto.robot import Status from viam.resource.registry import Registry, ResourceRegistration from viam.utils import message_to_struct @@ -10,6 +9,7 @@ __all__ = [ "Gripper", + "Geometry", ] diff --git a/src/viam/components/gripper/client.py b/src/viam/components/gripper/client.py index 6b40298cd..50128686e 100644 --- a/src/viam/components/gripper/client.py +++ b/src/viam/components/gripper/client.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Mapping, Optional +from typing import Any, Dict, List, Mapping, Optional from grpclib.client import Channel @@ -13,9 +13,10 @@ StopRequest, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict from .gripper import Gripper +from . import Geometry class GripperClient(Gripper, ReconfigurableResourceRPCClientBase): @@ -56,3 +57,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/gripper/gripper.py b/src/viam/components/gripper/gripper.py index 8c53114ed..c618f502b 100644 --- a/src/viam/components/gripper/gripper.py +++ b/src/viam/components/gripper/gripper.py @@ -1,9 +1,11 @@ import abc -from typing import Any, Dict, Final, Optional +from typing import Any, Dict, Final, List, Optional from viam.components.component_base import ComponentBase from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype +from . import Geometry + class Gripper(ComponentBase): """ @@ -67,3 +69,13 @@ async def is_moving(self) -> bool: bool: Whether the gripper is moving. """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the gripper, in their current configuration, in the frame of the gripper. + + Returns: + List[Geometry]: The geometries associated with the gripper. + """ + ... diff --git a/src/viam/components/gripper/service.py b/src/viam/components/gripper/service.py index bdcce24bf..b589e4c40 100644 --- a/src/viam/components/gripper/service.py +++ b/src/viam/components/gripper/service.py @@ -1,6 +1,5 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.gripper import ( GrabRequest, @@ -73,4 +72,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + arm = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await arm.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/input/__init__.py b/src/viam/components/input/__init__.py index 41dcee24c..60df0ddca 100644 --- a/src/viam/components/input/__init__.py +++ b/src/viam/components/input/__init__.py @@ -1,3 +1,4 @@ +from viam.proto.common import Geometry from viam.proto.component.inputcontroller import Status as InputStatus from viam.proto.robot import Status from viam.resource.registry import Registry, ResourceRegistration @@ -13,6 +14,7 @@ "ControlFunction", "Event", "EventType", + "Geometry", ] diff --git a/src/viam/components/input/client.py b/src/viam/components/input/client.py index 2c519c124..3d661c43f 100644 --- a/src/viam/components/input/client.py +++ b/src/viam/components/input/client.py @@ -22,9 +22,10 @@ TriggerEventRequest, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict from .input import Control, ControlFunction, Controller, Event, EventType +from . import Geometry LOGGER = getLogger(__name__) @@ -160,3 +161,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/input/input.py b/src/viam/components/input/input.py index 92c2a3158..180c8a156 100644 --- a/src/viam/components/input/input.py +++ b/src/viam/components/input/input.py @@ -12,6 +12,8 @@ from viam.proto.component.inputcontroller import Event as PBEvent from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype +from . import Geometry + class EventType(str, Enum): """ @@ -192,3 +194,13 @@ async def trigger_event(self, event: Event, *, extra: Optional[Dict[str, Any]] = event (Event): The event to trigger """ raise NotSupportedError(f"Input controller named {self.name} does not support triggering events") + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the input controller, in their current configuration, in the frame of the input controller. + + Returns: + List[Geometry]: The geometries associated with the input. + """ + ... diff --git a/src/viam/components/input/service.py b/src/viam/components/input/service.py index 651a5504e..17f81e52a 100644 --- a/src/viam/components/input/service.py +++ b/src/viam/components/input/service.py @@ -6,7 +6,7 @@ from h2.exceptions import StreamClosedError import viam -from viam.errors import MethodNotImplementedError, NotSupportedError +from viam.errors import NotSupportedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.inputcontroller import ( GetControlsRequest, @@ -166,4 +166,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + arm = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await arm.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/motor/__init__.py b/src/viam/components/motor/__init__.py index 5f6fb607b..bcdf8d34d 100644 --- a/src/viam/components/motor/__init__.py +++ b/src/viam/components/motor/__init__.py @@ -1,5 +1,6 @@ import asyncio +from viam.proto.common import Geometry from viam.proto.component.motor import Status as MotorStatus from viam.proto.robot import Status from viam.resource.registry import Registry, ResourceRegistration @@ -11,6 +12,7 @@ __all__ = [ "Motor", + "Geometry", ] diff --git a/src/viam/components/motor/client.py b/src/viam/components/motor/client.py index d630033a8..9bbd55318 100644 --- a/src/viam/components/motor/client.py +++ b/src/viam/components/motor/client.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Mapping, Optional, Tuple +from typing import Any, Dict, List, Mapping, Optional, Tuple from grpclib.client import Channel @@ -20,9 +20,10 @@ StopRequest, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict from .motor import Motor +from . import Geometry class MotorClient(Motor, ReconfigurableResourceRPCClientBase): @@ -141,3 +142,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/motor/motor.py b/src/viam/components/motor/motor.py index 46c90acfc..213fed836 100644 --- a/src/viam/components/motor/motor.py +++ b/src/viam/components/motor/motor.py @@ -1,10 +1,11 @@ import abc from dataclasses import dataclass -from typing import Any, Dict, Final, Optional, Tuple +from typing import Any, Dict, Final, List, Optional, Tuple from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase +from . import Geometry class Motor(ComponentBase): @@ -176,3 +177,13 @@ async def is_moving(self) -> bool: bool: Whether the motor is moving. """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the motor, in their current configuration, in the frame of the motor. + + Returns: + List[Geometry]: The geometries associated with the motor. + """ + ... diff --git a/src/viam/components/motor/service.py b/src/viam/components/motor/service.py index cb432afec..26073b528 100644 --- a/src/viam/components/motor/service.py +++ b/src/viam/components/motor/service.py @@ -1,6 +1,5 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.motor import ( GetPositionRequest, @@ -131,4 +130,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + motor = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await motor.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/movement_sensor/__init__.py b/src/viam/components/movement_sensor/__init__.py index e934b5fc0..f74a62157 100644 --- a/src/viam/components/movement_sensor/__init__.py +++ b/src/viam/components/movement_sensor/__init__.py @@ -1,4 +1,4 @@ -from viam.proto.common import GeoPoint, Orientation, Vector3 +from viam.proto.common import Geometry, GeoPoint, Orientation, Vector3 from viam.resource.registry import Registry, ResourceRegistration from .client import MovementSensorClient @@ -7,6 +7,7 @@ __all__ = [ "MovementSensor", + "Geometry", "GeoPoint", "Orientation", "Vector3", diff --git a/src/viam/components/movement_sensor/client.py b/src/viam/components/movement_sensor/client.py index a4a2a8d80..9fc32a8fd 100644 --- a/src/viam/components/movement_sensor/client.py +++ b/src/viam/components/movement_sensor/client.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Mapping, Optional, Tuple +from typing import Any, Dict, List, Mapping, Optional, Tuple from grpclib.client import Channel @@ -24,9 +24,9 @@ MovementSensorServiceStub, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict -from . import GeoPoint, Orientation, Vector3 +from . import Geometry, GeoPoint, Orientation, Vector3 class MovementSensorClient(MovementSensor, ReconfigurableResourceRPCClientBase): @@ -102,3 +102,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/movement_sensor/movement_sensor.py b/src/viam/components/movement_sensor/movement_sensor.py index d0f778313..4487c4f18 100644 --- a/src/viam/components/movement_sensor/movement_sensor.py +++ b/src/viam/components/movement_sensor/movement_sensor.py @@ -11,7 +11,7 @@ from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..sensor import Sensor -from . import GeoPoint, Orientation, Vector3 +from . import Geometry, GeoPoint, Orientation, Vector3 class MovementSensor(Sensor): @@ -182,3 +182,13 @@ def add_reading(name: str, reading, returntype: List) -> None: add_reading("orientation", orient, [Orientation]) return readings + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the movement sensor, in their current configuration, in the frame of the movement sensor. + + Returns: + List[Geometry]: The geometries associated with the movement sensor. + """ + ... diff --git a/src/viam/components/movement_sensor/service.py b/src/viam/components/movement_sensor/service.py index 26c2e4991..f32c9c5c6 100644 --- a/src/viam/components/movement_sensor/service.py +++ b/src/viam/components/movement_sensor/service.py @@ -1,7 +1,6 @@ from grpclib.server import Stream from viam.components.movement_sensor.movement_sensor import MovementSensor -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.movementsensor import ( GetAccuracyRequest, @@ -122,4 +121,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + sensor = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await sensor.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/pose_tracker/__init__.py b/src/viam/components/pose_tracker/__init__.py index 15c64bf45..ce816c0f4 100644 --- a/src/viam/components/pose_tracker/__init__.py +++ b/src/viam/components/pose_tracker/__init__.py @@ -1,3 +1,4 @@ +from viam.proto.common import Geometry from viam.resource.registry import Registry, ResourceRegistration from .client import PoseTrackerClient @@ -6,6 +7,7 @@ __all__ = [ "PoseTracker", + "Geometry", ] Registry.register_subtype( diff --git a/src/viam/components/pose_tracker/client.py b/src/viam/components/pose_tracker/client.py index 52dffbaeb..38f01d26f 100644 --- a/src/viam/components/pose_tracker/client.py +++ b/src/viam/components/pose_tracker/client.py @@ -5,9 +5,10 @@ from viam.proto.common import DoCommandRequest, DoCommandResponse, PoseInFrame from viam.proto.component.posetracker import GetPosesRequest, GetPosesResponse, PoseTrackerServiceStub from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict from .pose_tracker import PoseTracker +from . import Geometry class PoseTrackerClient(PoseTracker, ReconfigurableResourceRPCClientBase): @@ -37,3 +38,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/pose_tracker/pose_tracker.py b/src/viam/components/pose_tracker/pose_tracker.py index 5518bcf42..c180b98de 100644 --- a/src/viam/components/pose_tracker/pose_tracker.py +++ b/src/viam/components/pose_tracker/pose_tracker.py @@ -5,6 +5,7 @@ from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase +from . import Geometry class PoseTracker(ComponentBase): @@ -36,3 +37,13 @@ async def get_poses( an empty list, all available poses are returned. """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the pose tracker, in their current configuration, in the frame of the pose tracker. + + Returns: + List[Geometry]: The geometries associated with the pose tracker. + """ + ... diff --git a/src/viam/components/pose_tracker/service.py b/src/viam/components/pose_tracker/service.py index 284779f7e..5f2a4d571 100644 --- a/src/viam/components/pose_tracker/service.py +++ b/src/viam/components/pose_tracker/service.py @@ -1,6 +1,5 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.posetracker import GetPosesRequest, GetPosesResponse, PoseTrackerServiceBase from viam.resource.rpc_service_base import ResourceRPCServiceBase @@ -37,4 +36,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + pose_tracker = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await pose_tracker.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/power_sensor/power_sensor.py b/src/viam/components/power_sensor/power_sensor.py index c095d48eb..ddda6783e 100644 --- a/src/viam/components/power_sensor/power_sensor.py +++ b/src/viam/components/power_sensor/power_sensor.py @@ -3,14 +3,13 @@ from typing import Any, Dict, Final, List, Mapping, Optional, Tuple from grpclib import GRPCError +from viam.components.component_base import ComponentBase from viam.errors import MethodNotImplementedError, NotSupportedError from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype -from ..sensor import Sensor - -class PowerSensor(Sensor): +class PowerSensor(ComponentBase): """PowerSensor reports information about voltage, current and power. This acts as an abstract base class for any sensors that can provide data regarding voltage, current and/or power. diff --git a/src/viam/components/sensor/__init__.py b/src/viam/components/sensor/__init__.py index 1bdf2319a..0bf6fb1bb 100644 --- a/src/viam/components/sensor/__init__.py +++ b/src/viam/components/sensor/__init__.py @@ -1,3 +1,4 @@ +from viam.proto.common import Geometry from viam.resource.registry import Registry, ResourceRegistration from .client import SensorClient @@ -6,6 +7,7 @@ __all__ = [ "Sensor", + "Geometry", ] Registry.register_subtype( diff --git a/src/viam/components/sensor/client.py b/src/viam/components/sensor/client.py index 1d09b46d2..5f5f30b3e 100644 --- a/src/viam/components/sensor/client.py +++ b/src/viam/components/sensor/client.py @@ -1,13 +1,14 @@ -from typing import Any, Mapping, Optional +from typing import Any, Dict, List, Mapping, Optional from grpclib.client import Channel from viam.proto.common import DoCommandRequest, DoCommandResponse from viam.proto.component.sensor import GetReadingsRequest, GetReadingsResponse, SensorServiceStub from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, sensor_readings_value_to_native, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, sensor_readings_value_to_native, struct_to_dict from .sensor import Sensor +from . import Geometry class SensorClient(Sensor, ReconfigurableResourceRPCClientBase): @@ -31,3 +32,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/sensor/sensor.py b/src/viam/components/sensor/sensor.py index 4e0c9837d..a454d6b3c 100644 --- a/src/viam/components/sensor/sensor.py +++ b/src/viam/components/sensor/sensor.py @@ -1,9 +1,10 @@ import abc -from typing import Any, Mapping, Optional +from typing import Any, List, Mapping, Optional from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase +from . import Geometry class Sensor(ComponentBase): @@ -28,3 +29,13 @@ async def get_readings( Mapping[str, Any]: The measurements. Can be of any type. """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the sensor, in their current configuration, in the frame of the sensor. + + Returns: + List[Geometry]: The geometries associated with the sensor. + """ + ... diff --git a/src/viam/components/sensor/service.py b/src/viam/components/sensor/service.py index b4ffe6490..6ebdd3311 100644 --- a/src/viam/components/sensor/service.py +++ b/src/viam/components/sensor/service.py @@ -1,6 +1,5 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.sensor import GetReadingsRequest, GetReadingsResponse, SensorServiceBase from viam.resource.rpc_service_base import ResourceRPCServiceBase @@ -36,4 +35,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + sensor = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await sensor.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/servo/__init__.py b/src/viam/components/servo/__init__.py index 93546e85e..9ffe2975a 100644 --- a/src/viam/components/servo/__init__.py +++ b/src/viam/components/servo/__init__.py @@ -1,5 +1,6 @@ import asyncio +from viam.proto.common import Geometry from viam.proto.component.servo import Status as ServoStatus from viam.proto.robot import Status from viam.resource.registry import Registry, ResourceRegistration @@ -11,6 +12,7 @@ __all__ = [ "Servo", + "Geometry", ] diff --git a/src/viam/components/servo/client.py b/src/viam/components/servo/client.py index ad836c0ba..5bf639c60 100644 --- a/src/viam/components/servo/client.py +++ b/src/viam/components/servo/client.py @@ -1,4 +1,4 @@ -from typing import Any, Mapping, Optional +from typing import Any, Dict, List, Mapping, Optional from grpclib.client import Channel @@ -13,9 +13,10 @@ StopRequest, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase -from viam.utils import ValueTypes, dict_to_struct, struct_to_dict +from viam.utils import ValueTypes, get_geometries, dict_to_struct, struct_to_dict from .servo import Servo +from . import Geometry class ServoClient(Servo, ReconfigurableResourceRPCClientBase): @@ -56,3 +57,6 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) return struct_to_dict(response.result) + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + return await get_geometries(self, extra, timeout) diff --git a/src/viam/components/servo/service.py b/src/viam/components/servo/service.py index 6118306d7..bd9672d31 100644 --- a/src/viam/components/servo/service.py +++ b/src/viam/components/servo/service.py @@ -1,6 +1,5 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.servo import ( GetPositionRequest, @@ -72,4 +71,10 @@ async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) - await stream.send_message(response) async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None: - raise MethodNotImplementedError("GetGeometries").grpc_error + request = await stream.recv_message() + assert request is not None + servo = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + geometries = await servo.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) + response = GetGeometriesResponse(geometries=geometries) + await stream.send_message(response) diff --git a/src/viam/components/servo/servo.py b/src/viam/components/servo/servo.py index f858f158c..880135d1c 100644 --- a/src/viam/components/servo/servo.py +++ b/src/viam/components/servo/servo.py @@ -1,9 +1,10 @@ import abc -from typing import Any, Final, Mapping, Optional +from typing import Any, Final, List, Mapping, Optional from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, Subtype from ..component_base import ComponentBase +from . import Geometry class Servo(ComponentBase): @@ -53,3 +54,13 @@ async def is_moving(self) -> bool: bool: Whether the servo is moving. """ ... + + @abc.abstractmethod + async def get_geometries(self) -> List[Geometry]: + """ + Get all geometries associated with the servo, in their current configuration, in the frame of the servo. + + Returns: + List[Geometry]: The geometries associated with the servo. + """ + ... diff --git a/src/viam/module/types.py b/src/viam/module/types.py index 41a083030..baddf3384 100644 --- a/src/viam/module/types.py +++ b/src/viam/module/types.py @@ -15,10 +15,19 @@ def reconfigure(self, config: ComponentConfig, dependencies: Mapping[ResourceNam @runtime_checkable class Stoppable(Protocol): - """The Stoppable protocol defines the requirements for making a resource Stoppable. + """ + The Stoppable protocol defines the requirements for making a resource Stoppable. All resources that physically move should be Stoppable. """ def stop(self, *, extra: Optional[Mapping[str, Any]] = None, timeout: Optional[float] = None, **kwargs): ... + + +@runtime_checkable +class SupportsGetGeometries(Protocol): + """The SupportsGetGeometries protocol defines the requirements for a resource to call get_geometries.""" + + def get_geometries(self, *, extra: Optional[Mapping[str, Any]] = None, timeout: Optional[float] = None, **kwargs): + ... diff --git a/src/viam/utils.py b/src/viam/utils.py index 87b2fd275..ff95f7669 100644 --- a/src/viam/utils.py +++ b/src/viam/utils.py @@ -4,14 +4,14 @@ import sys import threading from datetime import datetime -from typing import Any, Dict, List, Mapping, SupportsBytes, SupportsFloat, Type, TypeVar, Union +from typing import Any, Dict, List, Mapping, Optional, SupportsBytes, SupportsFloat, Type, TypeVar, Union from google.protobuf.json_format import MessageToDict, ParseDict from google.protobuf.message import Message from google.protobuf.struct_pb2 import ListValue, Struct, Value from google.protobuf.timestamp_pb2 import Timestamp -from viam.proto.common import GeoPoint, Orientation, ResourceName, Vector3 +from viam.proto.common import Geometry, GeoPoint, GetGeometriesRequest, GetGeometriesResponse, Orientation, ResourceName, Vector3 from viam.resource.base import ResourceBase from viam.resource.registry import Registry from viam.resource.types import Subtype @@ -159,6 +159,14 @@ def datetime_to_timestamp(dt: datetime) -> Timestamp: return timestamp +async def get_geometries(client, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + if extra is None: + extra = {} + request = GetGeometriesRequest(name=client.name, extra=dict_to_struct(extra)) + response: GetGeometriesResponse = await client.client.GetGeometries(request, timeout=timeout) + return [geometry for geometry in response.geometries] + + def sensor_readings_native_to_value(readings: Mapping[str, Any]) -> Mapping[str, Any]: prim_readings = dict(readings) for key, reading in readings.items(): diff --git a/tests/mocks/components.py b/tests/mocks/components.py index f7625f5fc..46f67a49f 100644 --- a/tests/mocks/components.py +++ b/tests/mocks/components.py @@ -38,12 +38,15 @@ from viam.proto.common import ( AnalogStatus, BoardStatus, + Capsule, DigitalInterruptStatus, + Geometry, GeoPoint, Orientation, Pose, PoseInFrame, ResponseMetadata, + Sphere, Vector3, ) from viam.proto.component.audioinput import AudioChunk, AudioChunkInfo, SampleFormat @@ -52,20 +55,19 @@ from viam.utils import ValueTypes +GEOMETRIES = [ + Geometry(center=Pose(x=1, y=2, z=3, o_x=2, o_y=3, o_z=4, theta=20), sphere=Sphere(radius_mm=2)), + Geometry(center=Pose(x=1, y=2, z=3, o_x=2, o_y=3, o_z=4, theta=20), capsule=Capsule(radius_mm=3, length_mm=8)), +] + + class MockArm(Arm): def __init__(self, name: str): - self.position = Pose( - x=1, - y=2, - z=3, - o_x=2, - o_y=3, - o_z=4, - theta=20, - ) + self.position = Pose(x=1, y=2, z=3, o_x=2, o_y=3, o_z=4, theta=20) self.joint_positions = JointPositions(values=[0, 0, 0, 0, 0, 0]) self.is_stopped = True self.kinematics = (KinematicsFileFormat.KINEMATICS_FILE_FORMAT_SVA, b"\x00\x01\x02") + self.geometries = GEOMETRIES self.extra = None self.timeout: Optional[float] = None super().__init__(name) @@ -115,8 +117,14 @@ async def get_kinematics( self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None ) -> Tuple[KinematicsFileFormat.ValueType, bytes]: self.extra = extra + self.timeout = timeout return self.kinematics + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -124,6 +132,7 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option class MockAudioInput(AudioInput): def __init__(self, name: str, properties: AudioInput.Properties): super().__init__(name) + self.geometries = GEOMETRIES self.properties = properties self.timeout: Optional[float] = None @@ -146,6 +155,11 @@ async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> self.timeout = timeout return self.properties + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -159,6 +173,7 @@ def __init__(self, name: str): self.angular_pwr = Vector3(x=0, y=0, z=0) self.linear_vel = Vector3(x=0, y=0, z=0) self.angular_vel = Vector3(x=0, y=0, z=0) + self.geometries = GEOMETRIES self.extra: Optional[Dict[str, Any]] = None self.timeout: Optional[float] = None self.props = Base.Properties(1.0, 1.0) @@ -221,6 +236,11 @@ async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> self.timeout = timeout return self.props + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -313,6 +333,7 @@ def __init__( ): self.analog_readers = analog_readers self.digital_interrupts = digital_interrupts + self.geometries = GEOMETRIES self.gpios = gpio_pins self.timeout: Optional[float] = None super().__init__(name) @@ -352,6 +373,11 @@ async def status(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optio async def model_attributes(self) -> Board.Attributes: return Board.Attributes(remote=True) + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -366,6 +392,7 @@ async def set_power_mode( class MockCamera(Camera): def __init__(self, name: str): self.image = Image.new("RGBA", (100, 100), "#AABBCCDD") + self.geometries = GEOMETRIES self.point_cloud = b"THIS IS A POINT CLOUD" self.props = Camera.Properties( False, @@ -406,6 +433,11 @@ async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> self.timeout = timeout return self.props + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -414,6 +446,7 @@ class MockEncoder(Encoder): def __init__(self, name: str): self.position: float = 0 self.position_type = PositionType.POSITION_TYPE_TICKS_COUNT + self.geometries = GEOMETRIES self.extra = None self.timeout: Optional[float] = None super().__init__(name) @@ -452,6 +485,11 @@ async def get_properties( self.timeout = timeout return Encoder.Properties(ticks_count_supported=True, angle_degrees_supported=False) + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -464,6 +502,7 @@ def __init__(self, name: str, position: List[float], lengths: List[float]): self.extra = None self.homed = True self.speeds = Optional[List[float]] + self.geometries = GEOMETRIES self.timeout: Optional[float] = None super().__init__(name) @@ -507,12 +546,23 @@ async def stop(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optiona async def is_moving(self) -> bool: return not self.is_stopped + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} class MockGeneric(GenericComponent): timeout: Optional[float] = None + geometries = GEOMETRIES + + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: self.timeout = timeout @@ -522,6 +572,7 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option class MockGripper(Gripper): def __init__(self, name: str): self.opened = False + self.geometries = GEOMETRIES self.extra = None self.is_stopped = True self.timeout: Optional[float] = None @@ -548,6 +599,11 @@ async def stop(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optiona async def is_moving(self) -> bool: return not self.is_stopped + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -557,6 +613,7 @@ def __init__(self, name: str): super().__init__(name) self.events: Dict[Control, Event] = {} self.callbacks: Dict[Control, Dict[EventType, Optional[ControlFunction]]] = {} + self.geometries = GEOMETRIES self.timeout: Optional[float] = None self.extra = None self.reg_extra = None @@ -614,6 +671,11 @@ async def trigger_event(self, event: Event, *, extra: Optional[Dict[str, Any]] = if callback: callback(event) + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -623,6 +685,7 @@ def __init__(self, name: str): self.position: float = 0 self.power = 0 self.powered = False + self.geometries = GEOMETRIES self.extra = None self.timeout: Optional[float] = None super().__init__(name) @@ -732,6 +795,11 @@ async def is_powered( async def is_moving(self) -> bool: return self.powered + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -760,6 +828,7 @@ def __init__( self.orientation = orientation self.properties = properties self.accuracy = accuracy + self.geometries = GEOMETRIES self.extra: Optional[Dict[str, Any]] = None self.timeout: Optional[float] = None @@ -811,6 +880,11 @@ async def get_accuracy( self.timeout = timeout return self.accuracy + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -837,6 +911,7 @@ def __init__(self, name: str, poses: List[MockPose]): pose_map[str(idx)] = pose self.poses_result = pose_map self.name = name + self.geometries = GEOMETRIES self.timeout: Optional[float] = None self.extra: Optional[Mapping[str, Any]] = None @@ -855,6 +930,11 @@ async def get_poses( self.extra = extra return result + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -866,6 +946,7 @@ def __init__(self, name: str, voltage: float, current: float, is_ac: bool, power self.current = current self.is_ac = is_ac self.power = power + self.geometries = GEOMETRIES self.extra: Optional[Dict[str, Any]] = None self.timeout: Optional[float] = None @@ -891,6 +972,7 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option class MockSensor(Sensor): def __init__(self, name: str, result: Mapping[str, Any] = {"a": 0, "b": {"foo": "bar"}, "c": [1, 8, 2], "d": "Hello world!"}): self.readings = result + self.geometries = GEOMETRIES self.extra: Optional[Mapping[str, Any]] = None self.timeout: Optional[float] = None super().__init__(name) @@ -902,6 +984,11 @@ async def get_readings( self.timeout = timeout return self.readings + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} @@ -910,6 +997,7 @@ class MockServo(Servo): def __init__(self, name: str): self.angle = 0 self.is_stopped = True + self.geometries = GEOMETRIES self.timeout: Optional[float] = None self.extra: Optional[Mapping[str, Any]] = None super().__init__(name) @@ -933,5 +1021,10 @@ async def stop(self, *, extra: Optional[Mapping[str, Any]] = None, timeout: Opti async def is_moving(self) -> bool: return not self.is_stopped + async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]: + self.extra = extra + self.timeout = timeout + return self.geometries + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} diff --git a/tests/test_arm.py b/tests/test_arm.py index 8b11c2361..c98456f26 100644 --- a/tests/test_arm.py +++ b/tests/test_arm.py @@ -1,10 +1,17 @@ import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.arm import ArmClient, ArmStatus, KinematicsFileFormat, create_status from viam.components.arm.service import ArmRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetKinematicsRequest, GetKinematicsResponse, Pose +from viam.proto.common import ( + DoCommandRequest, + DoCommandResponse, + GetGeometriesRequest, + GetGeometriesResponse, + GetKinematicsRequest, + GetKinematicsResponse, + Pose, +) from viam.proto.component.arm import ( ArmServiceStub, GetEndPositionRequest, @@ -22,7 +29,7 @@ from viam.utils import dict_to_struct, message_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockArm +from .mocks.components import GEOMETRIES, MockArm class TestArm: @@ -70,6 +77,11 @@ async def test_get_kinematics(self): assert kd == self.kinematics assert self.arm.extra == {"1": "2"} + @pytest.mark.asyncio + async def test_get_geometries(self): + geometries = await self.arm.get_geometries() + assert geometries == GEOMETRIES + @pytest.mark.asyncio async def test_do(self): command = {"command": "args"} @@ -175,9 +187,9 @@ async def test_get_kinematics(self): async def test_get_geometries(self): async with ChannelFor([self.service]) as channel: client = ArmServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=self.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES @pytest.mark.asyncio async def test_extra(self): @@ -254,6 +266,13 @@ async def test_get_kinematics(self): assert kd == self.kinematics assert self.arm.extra == {"1": "2"} + @pytest.mark.asyncio + async def test_get_geometries(self): + async with ChannelFor([self.service]) as channel: + client = ArmClient(self.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES + @pytest.mark.asyncio async def test_do(self): async with ChannelFor([self.service]) as channel: diff --git a/tests/test_audio_input.py b/tests/test_audio_input.py index bb61f36e6..9fbf128bc 100644 --- a/tests/test_audio_input.py +++ b/tests/test_audio_input.py @@ -8,7 +8,7 @@ from viam.components.audio_input import AudioInput, AudioInputClient, AudioInputRPCService from viam.components.generic.service import GenericRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.audioinput import ( AudioInputServiceStub, ChunksRequest, @@ -22,7 +22,7 @@ from viam.utils import dict_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockAudioInput +from .mocks.components import GEOMETRIES, MockAudioInput PROPERTIES = AudioInput.Properties( channel_count=2, @@ -76,6 +76,11 @@ async def test_do(self, audio_input: AudioInput): resp = await audio_input.do_command(command) assert resp == {"command": command} + @pytest.mark.asyncio + async def test_get_geometries(self, audio_input: AudioInput): + geometries = await audio_input.get_geometries() + assert geometries == GEOMETRIES + class TestService: @pytest.mark.asyncio @@ -129,12 +134,12 @@ async def test_do(self, audio_input: MockAudioInput, service: AudioInputRPCServi assert result == {"command": command} @pytest.mark.asyncio - async def test_get_geometries(self, service: AudioInputRPCService): + async def test_get_geometries(self, audio_input: MockAudioInput, service: AudioInputRPCService): async with ChannelFor([service]) as channel: client = AudioInputServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=audio_input.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -169,3 +174,10 @@ async def test_do(self, audio_input: AudioInput, service: AudioInputRPCService): command = {"command": "args"} resp = await client.do_command(command) assert resp == {"command": command} + + @pytest.mark.asyncio + async def test_get_geometries(self, audio_input: AudioInput, service: AudioInputRPCService): + async with ChannelFor([service]) as channel: + client = AudioInputClient(audio_input.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_base.py b/tests/test_base.py index 3b6103210..651f23d4b 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -1,13 +1,12 @@ from random import randint, random import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.base import BaseClient, Vector3, create_status from viam.components.base.service import BaseRPCService from viam.components.generic.service import GenericRPCService -from viam.proto.common import ActuatorStatus, DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import ActuatorStatus, DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.base import ( BaseServiceStub, IsMovingRequest, @@ -22,7 +21,7 @@ from viam.utils import dict_to_struct, message_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockBase +from .mocks.components import GEOMETRIES, MockBase @pytest.fixture(scope="function") @@ -128,6 +127,11 @@ async def test_extra(self, base: MockBase): await base.move_straight(1, 1, extra=extra) assert base.extra == extra + @pytest.mark.asyncio + async def test_get_geometries(self, base: MockBase): + geometries = await base.get_geometries() + assert geometries == GEOMETRIES + class TestService: @pytest.mark.asyncio @@ -268,12 +272,12 @@ async def test_do(self, base: MockBase, service: BaseRPCService): assert result == {"command": command} @pytest.mark.asyncio - async def test_get_geometries(self, service: BaseRPCService): + async def test_get_geometries(self, base: MockBase, service: BaseRPCService): async with ChannelFor([service]) as channel: client = BaseServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=base.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -371,3 +375,10 @@ async def test_extra(self, base: MockBase, service: BaseRPCService): extra = {"foo": "bar", "baz": [1, 2, 3]} await client.move_straight(1, 1, extra=extra) assert base.extra == extra + + @pytest.mark.asyncio + async def test_get_geometries(self, base: MockBase, service: BaseRPCService): + async with ChannelFor([service]) as channel: + client = BaseClient(base.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_board.py b/tests/test_board.py index 7c422544d..6af6e145e 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -10,7 +10,15 @@ from viam.components.board.service import BoardRPCService from viam.components.generic.service import GenericRPCService from viam.errors import ResourceNotFoundError -from viam.proto.common import AnalogStatus, BoardStatus, DigitalInterruptStatus, DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import ( + AnalogStatus, + BoardStatus, + DigitalInterruptStatus, + DoCommandRequest, + DoCommandResponse, + GetGeometriesRequest, + GetGeometriesResponse, +) from viam.proto.component.board import ( BoardServiceStub, GetDigitalInterruptValueRequest, @@ -36,7 +44,7 @@ from viam.utils import dict_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockAnalogReader, MockBoard, MockDigitalInterrupt, MockGPIOPin +from .mocks.components import GEOMETRIES, MockAnalogReader, MockBoard, MockDigitalInterrupt, MockGPIOPin @pytest.fixture(scope="function") @@ -131,6 +139,11 @@ async def test_set_power_mode(self, board: MockBoard): assert board.power_mode == pm_mode assert board.power_mode_duration == pm_duration + @pytest.mark.asyncio + async def test_get_geometries(self, board: MockBoard): + geometries = await board.get_geometries() + assert geometries == GEOMETRIES + class TestService: @pytest.mark.asyncio @@ -286,12 +299,12 @@ async def test_do(self, board: MockBoard, service: BoardRPCService): assert result == {"command": command} @pytest.mark.asyncio - async def test_get_geometries(self, service: BoardRPCService): + async def test_get_geometries(self, board: MockBoard, service: BoardRPCService): async with ChannelFor([service]) as channel: client = BoardServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=board.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES @pytest.mark.asyncio async def test_set_power_mode(self, board: MockBoard, service: BoardRPCService): @@ -407,6 +420,13 @@ async def test_set_power_mode(self, board: MockBoard, service: BoardRPCService): pm_duration.FromTimedelta(pm_timedelta) assert board.power_mode_duration == pm_duration.ToTimedelta() + @pytest.mark.asyncio + async def test_extra(self, board: MockBoard, service: BoardRPCService): + async with ChannelFor([service]) as channel: + client = BoardClient(board.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES + class TestGPIOPinClient: @pytest.mark.asyncio diff --git a/tests/test_camera.py b/tests/test_camera.py index 1e776afb3..0693d8837 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -4,7 +4,6 @@ import pytest from google.api.httpbody_pb2 import HttpBody from google.protobuf.timestamp_pb2 import Timestamp -from grpclib import GRPCError from grpclib.testing import ChannelFor from PIL import Image @@ -12,7 +11,7 @@ from viam.components.camera.service import CameraRPCService from viam.components.generic.service import GenericRPCService from viam.media.video import LIBRARY_SUPPORTED_FORMATS, CameraMimeType, NamedImage, RawImage -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, ResponseMetadata +from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse, ResponseMetadata from viam.proto.component.camera import ( CameraServiceStub, DistortionParameters, @@ -32,7 +31,7 @@ from viam.utils import dict_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockCamera +from .mocks.components import GEOMETRIES, MockCamera # ################################ NB ################################# # # These test values have to be fixtures and must match the values in # @@ -133,6 +132,11 @@ async def test_timeout(self, camera: MockCamera): await camera.get_properties(timeout=7.86) assert camera.timeout == loose_approx(7.86) + @pytest.mark.asyncio + async def test_get_geometries(self, camera: MockCamera): + geometries = await camera.get_geometries() + assert geometries == GEOMETRIES + class TestService: @pytest.mark.asyncio @@ -226,12 +230,12 @@ async def test_do(self, camera: MockCamera, service: CameraRPCService): assert result == {"command": command} @pytest.mark.asyncio - async def test_get_geometries(self, service: CameraRPCService): + async def test_get_geometries(self, camera: MockCamera, service: CameraRPCService): async with ChannelFor([service]) as channel: client = CameraServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=camera.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -303,3 +307,10 @@ async def test_do(self, service: CameraRPCService): command = {"command": "args"} resp = await client.do_command(command) assert resp == {"command": command} + + @pytest.mark.asyncio + async def test_get_geometries(self, service: CameraRPCService): + async with ChannelFor([service]) as channel: + client = CameraClient("camera", channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_encoder.py b/tests/test_encoder.py index a85e0a78f..a8417a440 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -1,11 +1,10 @@ import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.encoder import EncoderClient from viam.components.encoder.service import EncoderRPCService from viam.components.generic.service import GenericRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.encoder import ( EncoderServiceStub, GetPositionRequest, @@ -19,7 +18,7 @@ from viam.utils import dict_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockEncoder +from .mocks.components import GEOMETRIES, MockEncoder @pytest.fixture(scope="function") @@ -66,6 +65,11 @@ async def test_do(self, encoder: MockEncoder): resp = await encoder.do_command(command) assert resp == {"command": command} + @pytest.mark.asyncio + async def test_get_geometries(self, encoder: MockEncoder): + geometries = await encoder.get_geometries() + assert geometries == GEOMETRIES + class TestService: @pytest.mark.asyncio @@ -108,12 +112,12 @@ async def test_do(self, encoder: MockEncoder, service: EncoderRPCService): assert result == {"command": command} @pytest.mark.asyncio - async def test_get_geometries(self, service: EncoderRPCService): + async def test_get_geometries(self, encoder: MockEncoder, service: EncoderRPCService): async with ChannelFor([service]) as channel: client = EncoderServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=encoder.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -150,3 +154,10 @@ async def test_do(self, encoder: MockEncoder, service: EncoderRPCService): command = {"command": "args"} resp = await client.do_command(command) assert resp == {"command": command} + + @pytest.mark.asyncio + async def test_get_geometries(self, encoder: MockEncoder, service: EncoderRPCService): + async with ChannelFor([service]) as channel: + client = EncoderClient(encoder.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_gantry.py b/tests/test_gantry.py index 0289587f9..ce331fe41 100644 --- a/tests/test_gantry.py +++ b/tests/test_gantry.py @@ -1,10 +1,9 @@ import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.gantry import GantryClient, GantryStatus, create_status from viam.components.gantry.service import GantryRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.gantry import ( GantryServiceStub, GetLengthsRequest, @@ -22,7 +21,7 @@ from viam.utils import dict_to_struct, message_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockGantry +from .mocks.components import GEOMETRIES, MockGantry class TestGantry: @@ -97,6 +96,11 @@ async def test_timeout(self): await self.gantry.stop(timeout=4.4) assert self.gantry.timeout == loose_approx(4.4) + @pytest.mark.asyncio + async def test_get_geometries(self): + geometries = await self.gantry.get_geometries() + assert geometries == GEOMETRIES + class TestService: @classmethod @@ -186,9 +190,9 @@ async def test_do(self): async def test_get_geometries(self): async with ChannelFor([self.service]) as channel: client = GantryServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=self.gantry.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -264,3 +268,10 @@ async def test_extra(self): extra = {"foo": "bar", "baz": [1, 2, 3]} await client.move_to_position([1, 2, 3], [4, 5, 6], extra=extra) assert self.gantry.extra == extra + + @pytest.mark.asyncio + async def test_get_geometries(self): + async with ChannelFor([self.service]) as channel: + client = GantryClient(self.gantry.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_generic.py b/tests/test_generic.py index 0e5021963..60220db94 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -1,15 +1,14 @@ import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.generic import GenericClient, GenericRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.generic import GenericServiceStub from viam.resource.manager import ResourceManager from viam.utils import dict_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockGeneric +from .mocks.components import GEOMETRIES, MockGeneric class TestGeneric: @@ -21,6 +20,11 @@ async def test_do(self): assert result == {"command": True} assert self.generic.timeout == loose_approx(1.82) + @pytest.mark.asyncio + async def test_get_geometries(self): + geometries = await self.generic.get_geometries() + assert geometries == GEOMETRIES + class TestService: @classmethod @@ -44,9 +48,9 @@ async def test_do(self): async def test_get_geometries(self): async with ChannelFor([self.service]) as channel: client = GenericServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=self.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -64,3 +68,10 @@ async def test_do(self): result = await client.do_command({"command": "args"}, timeout=7.86) assert result == {"command": True} assert self.generic.timeout == loose_approx(7.86) + + @pytest.mark.asyncio + async def test_get_geometries(self): + async with ChannelFor([self.service]) as channel: + client = GenericClient(self.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_gripper.py b/tests/test_gripper.py index fb204b5fd..e643bda85 100644 --- a/tests/test_gripper.py +++ b/tests/test_gripper.py @@ -1,11 +1,10 @@ import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.generic.service import GenericRPCService from viam.components.gripper import Gripper, GripperClient, create_status from viam.components.gripper.service import GripperRPCService -from viam.proto.common import ActuatorStatus, DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import ActuatorStatus, DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.gripper import ( GrabRequest, GrabResponse, @@ -19,7 +18,7 @@ from viam.utils import dict_to_struct, message_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockGripper +from .mocks.components import GEOMETRIES, MockGripper @pytest.fixture(scope="function") @@ -89,6 +88,11 @@ async def test_extra(self, gripper: MockGripper): await gripper.open(timeout=1.1, extra=extra) assert gripper.extra == extra + @pytest.mark.asyncio + async def test_get_geometries(self, gripper: MockGripper): + geometries = await gripper.get_geometries() + assert geometries == GEOMETRIES + class TestService: @pytest.mark.asyncio @@ -156,12 +160,12 @@ async def test_do(self, gripper: MockGripper, service: GripperRPCService): assert result == {"command": command} @pytest.mark.asyncio - async def test_get_geometries(self, service: GripperRPCService): + async def test_get_geometries(self, gripper: MockGripper, service: GripperRPCService): async with ChannelFor([service]) as channel: client = GripperServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=gripper.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -218,3 +222,10 @@ async def test_extra(self, gripper: MockGripper, service: GripperRPCService): extra = {"foo": "bar", "baz": [1, 2, 3]} await client.open(timeout=1.1, extra=extra) assert gripper.extra == extra + + @pytest.mark.asyncio + async def test_get_geometries(self, gripper: MockGripper, service: GripperRPCService): + async with ChannelFor([service]) as channel: + client = GripperClient(gripper.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_input.py b/tests/test_input.py index 51291e405..f16084066 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -3,14 +3,13 @@ from time import time import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.generic.service import GenericRPCService from viam.components.input import Control, Event, EventType from viam.components.input.client import ControllerClient from viam.components.input.service import InputControllerRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.inputcontroller import ( GetControlsRequest, GetControlsResponse, @@ -25,7 +24,7 @@ from viam.utils import dict_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockInputController +from .mocks.components import GEOMETRIES, MockInputController @pytest.fixture(scope="function") @@ -111,6 +110,11 @@ async def test_do(self, controller: MockInputController): resp = await controller.do_command(command) assert resp == {"command": command} + @pytest.mark.asyncio + async def test_get_geometries(self, controller: MockInputController): + geometries = await controller.get_geometries() + assert geometries == GEOMETRIES + class TestService: @pytest.mark.asyncio @@ -218,12 +222,12 @@ async def test_do(self, controller: MockInputController, service: InputControlle assert result == {"command": command} @pytest.mark.asyncio - async def test_get_geometries(self, service: InputControllerRPCService): + async def test_get_geometries(self, controller: MockInputController, service: InputControllerRPCService): async with ChannelFor([service]) as channel: client = InputControllerServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=controller.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -339,3 +343,10 @@ async def test_channel_rest(self, controller: MockInputController, service: Inpu client.reset_channel(channel2) await asyncio.sleep(0.1) assert client._is_streaming is True # reset the channel should restart the callback stream + + @pytest.mark.asyncio + async def test_get_geometries(self, controller: MockInputController, service: InputControllerRPCService): + async with ChannelFor([service]) as channel: + client = ControllerClient(controller.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_motor.py b/tests/test_motor.py index 857b377fe..ae29bd802 100644 --- a/tests/test_motor.py +++ b/tests/test_motor.py @@ -1,11 +1,10 @@ import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.generic.service import GenericRPCService from viam.components.motor import MotorClient, MotorStatus, create_status from viam.components.motor.service import MotorRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.motor import ( GetPositionRequest, GetPositionResponse, @@ -26,7 +25,7 @@ from viam.utils import dict_to_struct, message_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockMotor +from .mocks.components import GEOMETRIES, MockMotor @pytest.fixture(scope="function") @@ -145,6 +144,11 @@ async def test_extra(self, motor: MockMotor): await motor.is_powered(extra=extra) assert motor.extra == extra + @pytest.mark.asyncio + async def test_get_geometries(self, motor: MockMotor): + geometries = await motor.get_geometries() + assert geometries == GEOMETRIES + class TestService: @pytest.mark.asyncio @@ -281,12 +285,12 @@ async def test_do(self, motor: MockMotor, service: MotorRPCService): assert result == {"command": command} @pytest.mark.asyncio - async def test_get_geometries(self, service: MotorRPCService): + async def test_get_geometries(self, motor: MockMotor, service: MotorRPCService): async with ChannelFor([service]) as channel: client = MotorServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=motor.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -402,3 +406,10 @@ async def test_extra(self, motor: MockMotor, service: MotorRPCService): extra = {"foo": "bar", "baz": [1, 2, 3]} await client.is_powered(extra=extra) assert motor.extra == extra + + @pytest.mark.asyncio + async def test_get_geometries(self, motor: MockMotor, service: MotorRPCService): + async with ChannelFor([service]) as channel: + client = MotorClient(motor.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_movement_sensor.py b/tests/test_movement_sensor.py index 9d6ee0ba6..64a9fb3c9 100644 --- a/tests/test_movement_sensor.py +++ b/tests/test_movement_sensor.py @@ -6,7 +6,15 @@ from viam.components.generic.service import GenericRPCService from viam.components.movement_sensor import MovementSensor, MovementSensorClient, MovementSensorRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GeoPoint, GetGeometriesRequest, Orientation, Vector3 +from viam.proto.common import ( + DoCommandRequest, + DoCommandResponse, + GeoPoint, + GetGeometriesRequest, + GetGeometriesResponse, + Orientation, + Vector3, +) from viam.proto.component.movementsensor import ( GetAccuracyRequest, GetAccuracyResponse, @@ -30,7 +38,7 @@ from viam.utils import dict_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockMovementSensor +from .mocks.components import GEOMETRIES, MockMovementSensor COORDINATE = GeoPoint(latitude=40.664679865782624, longitude=-73.97668056188789) ALTITUDE = 15 @@ -217,6 +225,11 @@ async def test_do(self, movement_sensor: MovementSensor): resp = await movement_sensor.do_command(command) assert resp == {"command": command} + @pytest.mark.asyncio + async def test_get_geometries(self, movement_sensor: MockMovementSensor): + geometries = await movement_sensor.get_geometries() + assert geometries == GEOMETRIES + class TestService: @pytest.mark.asyncio @@ -317,12 +330,12 @@ async def test_do(self, movement_sensor: MockMovementSensor, service: MovementSe assert result == {"command": command} @pytest.mark.asyncio - async def test_get_geometries(self, service: MovementSensorRPCService): + async def test_get_geometries(self, movement_sensor: MockMovementSensor, service: MovementSensorRPCService): async with ChannelFor([service]) as channel: client = MovementSensorServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=movement_sensor.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -432,3 +445,10 @@ async def test_do(self, movement_sensor: MovementSensor, service: MovementSensor command = {"command": "args"} resp = await client.do_command(command) assert resp == {"command": command} + + @pytest.mark.asyncio + async def test_get_geometries(self, movement_sensor: MockMovementSensor, service: MovementSensorRPCService): + async with ChannelFor([service]) as channel: + client = MovementSensorClient(movement_sensor.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_pose_tracker.py b/tests/test_pose_tracker.py index 242cf580b..7104627cb 100644 --- a/tests/test_pose_tracker.py +++ b/tests/test_pose_tracker.py @@ -1,16 +1,15 @@ import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.pose_tracker import PoseTrackerClient from viam.components.pose_tracker.service import PoseTrackerRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, Pose, PoseInFrame +from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse, Pose, PoseInFrame from viam.proto.component.posetracker import GetPosesRequest, GetPosesResponse, PoseTrackerServiceStub from viam.resource.manager import ResourceManager from viam.utils import dict_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockPose, MockPoseTracker +from .mocks.components import GEOMETRIES, MockPose, MockPoseTracker POSES = [ MockPose(X=1, Y=2, Z=3, o_X=2, o_Y=3, o_Z=4, theta=20), @@ -36,6 +35,11 @@ async def test_do(self): resp = await self.mock_pose_tracker.do_command(command) assert resp == {"command": command} + @pytest.mark.asyncio + async def test_get_geometries(self): + geometries = await self.mock_pose_tracker.get_geometries() + assert geometries == GEOMETRIES + class TestService: @classmethod @@ -72,9 +76,9 @@ async def test_do(self): async def test_get_geometries(self): async with ChannelFor([self.service]) as channel: client = PoseTrackerServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=self.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -103,3 +107,10 @@ async def test_do(self): command = {"command": "args"} resp = await client.do_command(command) assert resp == {"command": command} + + @pytest.mark.asyncio + async def test_get_geometries(self): + async with ChannelFor([self.service]) as channel: + client = PoseTrackerClient(self.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_robot.py b/tests/test_robot.py index 60abfce34..aa2db4c02 100644 --- a/tests/test_robot.py +++ b/tests/test_robot.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import pytest from google.protobuf.struct_pb2 import Struct, Value @@ -8,7 +8,7 @@ from grpclib.server import Stream from grpclib.testing import ChannelFor -from viam.components.arm import Arm, KinematicsFileFormat +from viam.components.arm import Arm, Geometry, KinematicsFileFormat from viam.components.arm.client import ArmClient from viam.components.motor import Motor from viam.components.movement_sensor import MovementSensor @@ -557,6 +557,11 @@ async def get_kinematics( ) -> Tuple[KinematicsFileFormat.ValueType, bytes]: return await self.actual_client.get_kinematics(timeout=timeout) + async def get_geometries( + self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None + ) -> List[Geometry]: + return await self.actual_client.get_geometries(timeout=timeout) + old_create_client = Registry._SUBTYPES[Arm.SUBTYPE].create_rpc_client Registry._SUBTYPES[Arm.SUBTYPE].create_rpc_client = lambda name, channel: FakeArmClient(name, channel) diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 99aae486c..bbd04de02 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -1,16 +1,15 @@ import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.sensor import SensorClient from viam.components.sensor.service import SensorRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.sensor import GetReadingsRequest, GetReadingsResponse, SensorServiceStub from viam.resource.manager import ResourceManager from viam.utils import dict_to_struct, primitive_to_value, struct_to_dict from . import loose_approx -from .mocks.components import MockSensor +from .mocks.components import GEOMETRIES, MockSensor READINGS = {"a": 1, "b": 2, "c": 3} EXTRA_PARAMS = {"foo": "bar", "baz": [1, 2, 3]} @@ -36,6 +35,11 @@ async def test_do(self, sensor): resp = await sensor.do_command(command) assert resp == {"command": command} + @pytest.mark.asyncio + async def test_get_geometries(self, sensor): + geometries = await sensor.get_geometries() + assert geometries == GEOMETRIES + @pytest.fixture(scope="function") def manager(sensor) -> ResourceManager: @@ -70,12 +74,12 @@ async def test_do(self, sensor: MockSensor, service: SensorRPCService): assert result == {"command": command} @pytest.mark.asyncio - async def test_get_geometries(self, service: SensorRPCService): + async def test_get_geometries(self, sensor: MockSensor, service: SensorRPCService): async with ChannelFor([service]) as channel: client = SensorServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=sensor.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -96,3 +100,10 @@ async def test_do(self, sensor, manager, service): command = {"command": "args"} resp = await client.do_command(command) assert resp == {"command": command} + + @pytest.mark.asyncio + async def test_get_geometries(self, sensor, service): + async with ChannelFor([service]) as channel: + client = SensorClient(sensor.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES diff --git a/tests/test_servo.py b/tests/test_servo.py index dc6fe691e..b52680fba 100644 --- a/tests/test_servo.py +++ b/tests/test_servo.py @@ -1,10 +1,9 @@ import pytest -from grpclib import GRPCError from grpclib.testing import ChannelFor from viam.components.servo import ServoClient, ServoStatus, create_status from viam.components.servo.service import ServoRPCService -from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest +from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse from viam.proto.component.servo import ( GetPositionRequest, GetPositionResponse, @@ -18,7 +17,7 @@ from viam.utils import dict_to_struct, message_to_struct, struct_to_dict from . import loose_approx -from .mocks.components import MockServo +from .mocks.components import GEOMETRIES, MockServo class TestServo: @@ -67,6 +66,11 @@ async def test_status(self): assert status.name == self.servo.get_resource_name(self.servo.name) assert status.status == message_to_struct(ServoStatus(position_deg=self.pos, is_moving=True)) + @pytest.mark.asyncio + async def test_get_geometries(self): + geometries = await self.servo.get_geometries() + assert geometries == GEOMETRIES + class TestService: @classmethod @@ -132,9 +136,9 @@ async def test_do(self): async def test_get_geometries(self): async with ChannelFor([self.service]) as channel: client = ServoServiceStub(channel) - request = GetGeometriesRequest() - with pytest.raises(GRPCError, match=r"Method [a-zA-Z]+ not implemented"): - await client.GetGeometries(request) + request = GetGeometriesRequest(name=self.servo.name) + response: GetGeometriesResponse = await client.GetGeometries(request) + assert [geometry for geometry in response.geometries] == GEOMETRIES class TestClient: @@ -189,3 +193,10 @@ async def test_do(self): command = {"command": "args"} resp = await client.do_command(command) assert resp == {"command": command} + + @pytest.mark.asyncio + async def test_get_geometries(self): + async with ChannelFor([self.service]) as channel: + client = ServoClient(self.name, channel) + geometries = await client.get_geometries() + assert geometries == GEOMETRIES