diff --git a/src/viam/components/arm/__init__.py b/src/viam/components/arm/__init__.py index 60278e410..b1d65d998 100644 --- a/src/viam/components/arm/__init__.py +++ b/src/viam/components/arm/__init__.py @@ -2,13 +2,14 @@ from viam.proto.component.arm import JointPositions from viam.resource.registry import Registry, ResourceRegistration -from .arm import Arm +from .arm import Arm, JointPositionStream from .client import ArmClient from .service import ArmRPCService __all__ = [ "Arm", "JointPositions", + "JointPositionStream", "KinematicsFileFormat", "Pose", ] diff --git a/src/viam/components/arm/arm.py b/src/viam/components/arm/arm.py index 5569bfafb..41113e847 100644 --- a/src/viam/components/arm/arm.py +++ b/src/viam/components/arm/arm.py @@ -1,12 +1,29 @@ import abc +from dataclasses import dataclass +from datetime import datetime from typing import Any, Dict, Final, Optional, Tuple from viam.resource.types import API, RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT +from viam.streams import Stream from ..component_base import ComponentBase from . import JointPositions, KinematicsFileFormat, Pose +@dataclass +class JointPositionStream: + """A single frame from a joint position stream.""" + + positions: JointPositions + """Current joint positions.""" + + timestamp: datetime + """Timestamp when positions were captured from the arm hardware.""" + + sequence: int + """Sequential message number, used to detect dropped messages.""" + + class Arm(ComponentBase): """ Arm represents a physical robot arm that exists in three-dimensional space. @@ -221,3 +238,35 @@ async def get_kinematics( For more information, see `Arm component `_. """ ... + + @abc.abstractmethod + async def stream_joint_positions( + self, + *, + fps: Optional[int] = None, + extra: Optional[Dict[str, Any]] = None, + timeout: Optional[float] = None, + **kwargs, + ) -> Stream[JointPositionStream]: + """ + Stream joint positions from the arm at the specified rate. + + :: + + my_arm = Arm.from_robot(robot=machine, name="my_arm") + + # Stream joint positions at 30 fps + stream = await my_arm.stream_joint_positions(fps=30) + async for position_frame in stream: + print(f"Positions: {position_frame.positions.values}") + print(f"Timestamp: {position_frame.timestamp}") + + Args: + fps: Target frames per second for the stream. If not specified, uses the arm's default rate. + extra: Additional arguments to the method. + timeout: Optional timeout in seconds. + + Returns: + Stream[JointPositionStream]: A stream of joint position frames containing positions, timestamps, and sequence numbers. + """ + ... diff --git a/src/viam/components/arm/client.py b/src/viam/components/arm/client.py index 37ad25e08..a62a0177b 100644 --- a/src/viam/components/arm/client.py +++ b/src/viam/components/arm/client.py @@ -1,6 +1,8 @@ +from datetime import datetime, timezone from typing import Any, Dict, List, Mapping, Optional, Tuple from grpclib.client import Channel +from grpclib.client import Stream as ClientStream from viam.proto.common import DoCommandRequest, DoCommandResponse, Geometry, GetKinematicsRequest, GetKinematicsResponse from viam.proto.component.arm import ( @@ -15,11 +17,15 @@ MoveToJointPositionsRequest, MoveToPositionRequest, StopRequest, + StreamJointPositionsRequest, + StreamJointPositionsResponse, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase +from viam.streams import Stream, StreamWithIterator from viam.utils import ValueTypes, dict_to_struct, get_geometries, struct_to_dict from . import Arm, KinematicsFileFormat, Pose +from .arm import JointPositionStream class ArmClient(Arm, ReconfigurableResourceRPCClientBase): @@ -122,3 +128,34 @@ async def get_kinematics( async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> List[Geometry]: md = kwargs.get("metadata", self.Metadata()) return await get_geometries(self.client, self.name, extra, timeout, md) + + async def stream_joint_positions( + self, + *, + fps: Optional[int] = None, + extra: Optional[Dict[str, Any]] = None, + timeout: Optional[float] = None, + **kwargs, + ) -> Stream[JointPositionStream]: + request = StreamJointPositionsRequest( + name=self.name, + fps=fps, + extra=dict_to_struct(extra), + ) + + async def read(): + md = kwargs.get("metadata", self.Metadata()).proto + stream: ClientStream[StreamJointPositionsRequest, StreamJointPositionsResponse] + async with self.client.StreamJointPositions.open(metadata=md) as stream: + await stream.send_message(request, end=True) + async for response in stream: + yield JointPositionStream( + positions=response.positions, + timestamp=datetime.fromtimestamp( + response.timestamp.seconds + response.timestamp.nanos / 1e9, + tz=timezone.utc, + ), + sequence=response.sequence, + ) + + return StreamWithIterator(read()) diff --git a/src/viam/components/arm/service.py b/src/viam/components/arm/service.py index 56e903daf..cf7f8e6e9 100644 --- a/src/viam/components/arm/service.py +++ b/src/viam/components/arm/service.py @@ -1,5 +1,8 @@ +from google.protobuf.timestamp_pb2 import Timestamp from grpclib.server import Stream +from h2.exceptions import StreamClosedError +from viam.logging import getLogger from viam.proto.common import ( DoCommandRequest, DoCommandResponse, @@ -15,12 +18,15 @@ GetJointPositionsResponse, IsMovingRequest, IsMovingResponse, + JointPositions, MoveToJointPositionsRequest, MoveToJointPositionsResponse, MoveToPositionRequest, MoveToPositionResponse, StopRequest, StopResponse, + StreamJointPositionsRequest, + StreamJointPositionsResponse, UnimplementedArmServiceBase, ) from viam.resource.rpc_service_base import ResourceRPCServiceBase @@ -28,6 +34,8 @@ from .arm import Arm +LOGGER = getLogger(__name__) + class ArmRPCService(UnimplementedArmServiceBase, ResourceRPCServiceBase[Arm]): """ @@ -121,3 +129,31 @@ async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometries geometries = await arm.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout) response = GetGeometriesResponse(geometries=geometries) await stream.send_message(response) + + async def StreamJointPositions( + self, stream: Stream[StreamJointPositionsRequest, StreamJointPositionsResponse] + ) -> None: + request = await stream.recv_message() + assert request is not None + arm = self.get_resource(request.name) + fps = request.fps if request.HasField("fps") else None + position_stream = await arm.stream_joint_positions( + fps=fps, + extra=struct_to_dict(request.extra), + metadata=stream.metadata, + ) + async for frame in position_stream: + try: + timestamp = Timestamp() + timestamp.FromDatetime(frame.timestamp) + response = StreamJointPositionsResponse( + positions=JointPositions(values=list(frame.positions.values)), + timestamp=timestamp, + sequence=frame.sequence, + ) + await stream.send_message(response) + except StreamClosedError: + return + except Exception as e: + LOGGER.error(e) + return diff --git a/src/viam/gen/component/arm/v1/arm_grpc.py b/src/viam/gen/component/arm/v1/arm_grpc.py index 45bac9ed7..83ff53e2b 100644 --- a/src/viam/gen/component/arm/v1/arm_grpc.py +++ b/src/viam/gen/component/arm/v1/arm_grpc.py @@ -8,6 +8,7 @@ from .... import common import google.api.annotations_pb2 import google.protobuf.struct_pb2 +import google.protobuf.timestamp_pb2 from .... import component class ArmServiceBase(abc.ABC): @@ -24,6 +25,10 @@ async def MoveToPosition(self, stream: 'grpclib.server.Stream[component.arm.v1.a async def GetJointPositions(self, stream: 'grpclib.server.Stream[component.arm.v1.arm_pb2.GetJointPositionsRequest, component.arm.v1.arm_pb2.GetJointPositionsResponse]') -> None: pass + @abc.abstractmethod + async def StreamJointPositions(self, stream: 'grpclib.server.Stream[component.arm.v1.arm_pb2.StreamJointPositionsRequest, component.arm.v1.arm_pb2.StreamJointPositionsResponse]') -> None: + pass + @abc.abstractmethod async def MoveToJointPositions(self, stream: 'grpclib.server.Stream[component.arm.v1.arm_pb2.MoveToJointPositionsRequest, component.arm.v1.arm_pb2.MoveToJointPositionsResponse]') -> None: pass @@ -57,7 +62,7 @@ async def Get3DModels(self, stream: 'grpclib.server.Stream[common.v1.common_pb2. pass def __mapping__(self) -> typing.Dict[str, grpclib.const.Handler]: - return {'/viam.component.arm.v1.ArmService/GetEndPosition': grpclib.const.Handler(self.GetEndPosition, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.GetEndPositionRequest, component.arm.v1.arm_pb2.GetEndPositionResponse), '/viam.component.arm.v1.ArmService/MoveToPosition': grpclib.const.Handler(self.MoveToPosition, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.MoveToPositionRequest, component.arm.v1.arm_pb2.MoveToPositionResponse), '/viam.component.arm.v1.ArmService/GetJointPositions': grpclib.const.Handler(self.GetJointPositions, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.GetJointPositionsRequest, component.arm.v1.arm_pb2.GetJointPositionsResponse), '/viam.component.arm.v1.ArmService/MoveToJointPositions': grpclib.const.Handler(self.MoveToJointPositions, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.MoveToJointPositionsRequest, component.arm.v1.arm_pb2.MoveToJointPositionsResponse), '/viam.component.arm.v1.ArmService/MoveThroughJointPositions': grpclib.const.Handler(self.MoveThroughJointPositions, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.MoveThroughJointPositionsRequest, component.arm.v1.arm_pb2.MoveThroughJointPositionsResponse), '/viam.component.arm.v1.ArmService/Stop': grpclib.const.Handler(self.Stop, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.StopRequest, component.arm.v1.arm_pb2.StopResponse), '/viam.component.arm.v1.ArmService/IsMoving': grpclib.const.Handler(self.IsMoving, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.IsMovingRequest, component.arm.v1.arm_pb2.IsMovingResponse), '/viam.component.arm.v1.ArmService/DoCommand': grpclib.const.Handler(self.DoCommand, grpclib.const.Cardinality.UNARY_UNARY, common.v1.common_pb2.DoCommandRequest, common.v1.common_pb2.DoCommandResponse), '/viam.component.arm.v1.ArmService/GetKinematics': grpclib.const.Handler(self.GetKinematics, grpclib.const.Cardinality.UNARY_UNARY, common.v1.common_pb2.GetKinematicsRequest, common.v1.common_pb2.GetKinematicsResponse), '/viam.component.arm.v1.ArmService/GetGeometries': grpclib.const.Handler(self.GetGeometries, grpclib.const.Cardinality.UNARY_UNARY, common.v1.common_pb2.GetGeometriesRequest, common.v1.common_pb2.GetGeometriesResponse), '/viam.component.arm.v1.ArmService/Get3DModels': grpclib.const.Handler(self.Get3DModels, grpclib.const.Cardinality.UNARY_UNARY, common.v1.common_pb2.Get3DModelsRequest, common.v1.common_pb2.Get3DModelsResponse)} + return {'/viam.component.arm.v1.ArmService/GetEndPosition': grpclib.const.Handler(self.GetEndPosition, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.GetEndPositionRequest, component.arm.v1.arm_pb2.GetEndPositionResponse), '/viam.component.arm.v1.ArmService/MoveToPosition': grpclib.const.Handler(self.MoveToPosition, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.MoveToPositionRequest, component.arm.v1.arm_pb2.MoveToPositionResponse), '/viam.component.arm.v1.ArmService/GetJointPositions': grpclib.const.Handler(self.GetJointPositions, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.GetJointPositionsRequest, component.arm.v1.arm_pb2.GetJointPositionsResponse), '/viam.component.arm.v1.ArmService/StreamJointPositions': grpclib.const.Handler(self.StreamJointPositions, grpclib.const.Cardinality.UNARY_STREAM, component.arm.v1.arm_pb2.StreamJointPositionsRequest, component.arm.v1.arm_pb2.StreamJointPositionsResponse), '/viam.component.arm.v1.ArmService/MoveToJointPositions': grpclib.const.Handler(self.MoveToJointPositions, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.MoveToJointPositionsRequest, component.arm.v1.arm_pb2.MoveToJointPositionsResponse), '/viam.component.arm.v1.ArmService/MoveThroughJointPositions': grpclib.const.Handler(self.MoveThroughJointPositions, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.MoveThroughJointPositionsRequest, component.arm.v1.arm_pb2.MoveThroughJointPositionsResponse), '/viam.component.arm.v1.ArmService/Stop': grpclib.const.Handler(self.Stop, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.StopRequest, component.arm.v1.arm_pb2.StopResponse), '/viam.component.arm.v1.ArmService/IsMoving': grpclib.const.Handler(self.IsMoving, grpclib.const.Cardinality.UNARY_UNARY, component.arm.v1.arm_pb2.IsMovingRequest, component.arm.v1.arm_pb2.IsMovingResponse), '/viam.component.arm.v1.ArmService/DoCommand': grpclib.const.Handler(self.DoCommand, grpclib.const.Cardinality.UNARY_UNARY, common.v1.common_pb2.DoCommandRequest, common.v1.common_pb2.DoCommandResponse), '/viam.component.arm.v1.ArmService/GetKinematics': grpclib.const.Handler(self.GetKinematics, grpclib.const.Cardinality.UNARY_UNARY, common.v1.common_pb2.GetKinematicsRequest, common.v1.common_pb2.GetKinematicsResponse), '/viam.component.arm.v1.ArmService/GetGeometries': grpclib.const.Handler(self.GetGeometries, grpclib.const.Cardinality.UNARY_UNARY, common.v1.common_pb2.GetGeometriesRequest, common.v1.common_pb2.GetGeometriesResponse), '/viam.component.arm.v1.ArmService/Get3DModels': grpclib.const.Handler(self.Get3DModels, grpclib.const.Cardinality.UNARY_UNARY, common.v1.common_pb2.Get3DModelsRequest, common.v1.common_pb2.Get3DModelsResponse)} class UnimplementedArmServiceBase(ArmServiceBase): @@ -70,6 +75,9 @@ async def MoveToPosition(self, stream: 'grpclib.server.Stream[component.arm.v1.a async def GetJointPositions(self, stream: 'grpclib.server.Stream[component.arm.v1.arm_pb2.GetJointPositionsRequest, component.arm.v1.arm_pb2.GetJointPositionsResponse]') -> None: raise grpclib.exceptions.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + async def StreamJointPositions(self, stream: 'grpclib.server.Stream[component.arm.v1.arm_pb2.StreamJointPositionsRequest, component.arm.v1.arm_pb2.StreamJointPositionsResponse]') -> None: + raise grpclib.exceptions.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + async def MoveToJointPositions(self, stream: 'grpclib.server.Stream[component.arm.v1.arm_pb2.MoveToJointPositionsRequest, component.arm.v1.arm_pb2.MoveToJointPositionsResponse]') -> None: raise grpclib.exceptions.GRPCError(grpclib.const.Status.UNIMPLEMENTED) @@ -100,6 +108,7 @@ def __init__(self, channel: grpclib.client.Channel) -> None: self.GetEndPosition = grpclib.client.UnaryUnaryMethod(channel, '/viam.component.arm.v1.ArmService/GetEndPosition', component.arm.v1.arm_pb2.GetEndPositionRequest, component.arm.v1.arm_pb2.GetEndPositionResponse) self.MoveToPosition = grpclib.client.UnaryUnaryMethod(channel, '/viam.component.arm.v1.ArmService/MoveToPosition', component.arm.v1.arm_pb2.MoveToPositionRequest, component.arm.v1.arm_pb2.MoveToPositionResponse) self.GetJointPositions = grpclib.client.UnaryUnaryMethod(channel, '/viam.component.arm.v1.ArmService/GetJointPositions', component.arm.v1.arm_pb2.GetJointPositionsRequest, component.arm.v1.arm_pb2.GetJointPositionsResponse) + self.StreamJointPositions = grpclib.client.UnaryStreamMethod(channel, '/viam.component.arm.v1.ArmService/StreamJointPositions', component.arm.v1.arm_pb2.StreamJointPositionsRequest, component.arm.v1.arm_pb2.StreamJointPositionsResponse) self.MoveToJointPositions = grpclib.client.UnaryUnaryMethod(channel, '/viam.component.arm.v1.ArmService/MoveToJointPositions', component.arm.v1.arm_pb2.MoveToJointPositionsRequest, component.arm.v1.arm_pb2.MoveToJointPositionsResponse) self.MoveThroughJointPositions = grpclib.client.UnaryUnaryMethod(channel, '/viam.component.arm.v1.ArmService/MoveThroughJointPositions', component.arm.v1.arm_pb2.MoveThroughJointPositionsRequest, component.arm.v1.arm_pb2.MoveThroughJointPositionsResponse) self.Stop = grpclib.client.UnaryUnaryMethod(channel, '/viam.component.arm.v1.ArmService/Stop', component.arm.v1.arm_pb2.StopRequest, component.arm.v1.arm_pb2.StopResponse) diff --git a/src/viam/gen/component/arm/v1/arm_pb2.py b/src/viam/gen/component/arm/v1/arm_pb2.py index 11ca46465..581325190 100644 --- a/src/viam/gen/component/arm/v1/arm_pb2.py +++ b/src/viam/gen/component/arm/v1/arm_pb2.py @@ -9,7 +9,8 @@ from ....common.v1 import common_pb2 as common_dot_v1_dot_common__pb2 from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2 from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1acomponent/arm/v1/arm.proto\x12\x15viam.component.arm.v1\x1a\x16common/v1/common.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1cgoogle/protobuf/struct.proto"Z\n\x15GetEndPositionRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extra"B\n\x16GetEndPositionResponse\x12(\n\x04pose\x18\x01 \x01(\x0b2\x14.viam.common.v1.PoseR\x04pose"(\n\x0eJointPositions\x12\x16\n\x06values\x18\x01 \x03(\x01R\x06values"]\n\x18GetJointPositionsRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extra"`\n\x19GetJointPositionsResponse\x12C\n\tpositions\x18\x01 \x01(\x0b2%.viam.component.arm.v1.JointPositionsR\tpositions"\x80\x01\n\x15MoveToPositionRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12$\n\x02to\x18\x02 \x01(\x0b2\x14.viam.common.v1.PoseR\x02to\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extra"\x18\n\x16MoveToPositionResponse"\xa5\x01\n\x1bMoveToJointPositionsRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12C\n\tpositions\x18\x02 \x01(\x0b2%.viam.component.arm.v1.JointPositionsR\tpositions\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extra"\x1e\n\x1cMoveToJointPositionsResponse"\xf9\x01\n MoveThroughJointPositionsRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12C\n\tpositions\x18\x02 \x03(\x0b2%.viam.component.arm.v1.JointPositionsR\tpositions\x12A\n\x07options\x18\x03 \x01(\x0b2".viam.component.arm.v1.MoveOptionsH\x00R\x07options\x88\x01\x01\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extraB\n\n\x08_options"#\n!MoveThroughJointPositionsResponse"P\n\x0bStopRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extra"\x0e\n\x0cStopResponse"\xae\x01\n\x06Status\x127\n\x0cend_position\x18\x01 \x01(\x0b2\x14.viam.common.v1.PoseR\x0bendPosition\x12N\n\x0fjoint_positions\x18\x02 \x01(\x0b2%.viam.component.arm.v1.JointPositionsR\x0ejointPositions\x12\x1b\n\tis_moving\x18\x03 \x01(\x08R\x08isMoving"%\n\x0fIsMovingRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name"/\n\x10IsMovingResponse\x12\x1b\n\tis_moving\x18\x01 \x01(\x08R\x08isMoving"\xac\x01\n\x0bMoveOptions\x123\n\x14max_vel_degs_per_sec\x18\x01 \x01(\x01H\x00R\x10maxVelDegsPerSec\x88\x01\x01\x125\n\x15max_acc_degs_per_sec2\x18\x02 \x01(\x01H\x01R\x11maxAccDegsPerSec2\x88\x01\x01B\x17\n\x15_max_vel_degs_per_secB\x18\n\x16_max_acc_degs_per_sec22\xff\r\n\nArmService\x12\xa1\x01\n\x0eGetEndPosition\x12,.viam.component.arm.v1.GetEndPositionRequest\x1a-.viam.component.arm.v1.GetEndPositionResponse"2\x82\xd3\xe4\x93\x02,\x12*/viam/api/v1/component/arm/{name}/position\x12\xa5\x01\n\x0eMoveToPosition\x12,.viam.component.arm.v1.MoveToPositionRequest\x1a-.viam.component.arm.v1.MoveToPositionResponse"6\xa0\x92)\x01\x82\xd3\xe4\x93\x02,\x1a*/viam/api/v1/component/arm/{name}/position\x12\xb1\x01\n\x11GetJointPositions\x12/.viam.component.arm.v1.GetJointPositionsRequest\x1a0.viam.component.arm.v1.GetJointPositionsResponse"9\x82\xd3\xe4\x93\x023\x121/viam/api/v1/component/arm/{name}/joint_positions\x12\xbe\x01\n\x14MoveToJointPositions\x122.viam.component.arm.v1.MoveToJointPositionsRequest\x1a3.viam.component.arm.v1.MoveToJointPositionsResponse"=\xa0\x92)\x01\x82\xd3\xe4\x93\x023\x1a1/viam/api/v1/component/arm/{name}/joint_positions\x12\xda\x01\n\x19MoveThroughJointPositions\x127.viam.component.arm.v1.MoveThroughJointPositionsRequest\x1a8.viam.component.arm.v1.MoveThroughJointPositionsResponse"J\xa0\x92)\x01\x82\xd3\xe4\x93\x02@">/viam/api/v1/component/arm/{name}/move_through_joint_positions\x12\x7f\n\x04Stop\x12".viam.component.arm.v1.StopRequest\x1a#.viam.component.arm.v1.StopResponse".\x82\xd3\xe4\x93\x02("&/viam/api/v1/component/arm/{name}/stop\x12\x90\x01\n\x08IsMoving\x12&.viam.component.arm.v1.IsMovingRequest\x1a\'.viam.component.arm.v1.IsMovingResponse"3\x82\xd3\xe4\x93\x02-\x12+/viam/api/v1/component/arm/{name}/is_moving\x12\x86\x01\n\tDoCommand\x12 .viam.common.v1.DoCommandRequest\x1a!.viam.common.v1.DoCommandResponse"4\x82\xd3\xe4\x93\x02.",/viam/api/v1/component/arm/{name}/do_command\x12\x92\x01\n\rGetKinematics\x12$.viam.common.v1.GetKinematicsRequest\x1a%.viam.common.v1.GetKinematicsResponse"4\x82\xd3\xe4\x93\x02.\x12,/viam/api/v1/component/arm/{name}/kinematics\x12\x92\x01\n\rGetGeometries\x12$.viam.common.v1.GetGeometriesRequest\x1a%.viam.common.v1.GetGeometriesResponse"4\x82\xd3\xe4\x93\x02.\x12,/viam/api/v1/component/arm/{name}/geometries\x12\x8b\x01\n\x0bGet3DModels\x12".viam.common.v1.Get3DModelsRequest\x1a#.viam.common.v1.Get3DModelsResponse"3\x82\xd3\xe4\x93\x02-\x12+/viam/api/v1/component/arm/{name}/3d_modelsB=\n\x19com.viam.component.arm.v1Z go.viam.com/api/component/arm/v1b\x06proto3') +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1acomponent/arm/v1/arm.proto\x12\x15viam.component.arm.v1\x1a\x16common/v1/common.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto"Z\n\x15GetEndPositionRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extra"B\n\x16GetEndPositionResponse\x12(\n\x04pose\x18\x01 \x01(\x0b2\x14.viam.common.v1.PoseR\x04pose"(\n\x0eJointPositions\x12\x16\n\x06values\x18\x01 \x03(\x01R\x06values"]\n\x18GetJointPositionsRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extra"`\n\x19GetJointPositionsResponse\x12C\n\tpositions\x18\x01 \x01(\x0b2%.viam.component.arm.v1.JointPositionsR\tpositions"\x7f\n\x1bStreamJointPositionsRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x15\n\x03fps\x18\x02 \x01(\x05H\x00R\x03fps\x88\x01\x01\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extraB\x06\n\x04_fps"\xb9\x01\n\x1cStreamJointPositionsResponse\x12C\n\tpositions\x18\x01 \x01(\x0b2%.viam.component.arm.v1.JointPositionsR\tpositions\x128\n\ttimestamp\x18\x02 \x01(\x0b2\x1a.google.protobuf.TimestampR\ttimestamp\x12\x1a\n\x08sequence\x18\x03 \x01(\x05R\x08sequence"\x80\x01\n\x15MoveToPositionRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12$\n\x02to\x18\x02 \x01(\x0b2\x14.viam.common.v1.PoseR\x02to\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extra"\x18\n\x16MoveToPositionResponse"\xa5\x01\n\x1bMoveToJointPositionsRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12C\n\tpositions\x18\x02 \x01(\x0b2%.viam.component.arm.v1.JointPositionsR\tpositions\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extra"\x1e\n\x1cMoveToJointPositionsResponse"\xf9\x01\n MoveThroughJointPositionsRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12C\n\tpositions\x18\x02 \x03(\x0b2%.viam.component.arm.v1.JointPositionsR\tpositions\x12A\n\x07options\x18\x03 \x01(\x0b2".viam.component.arm.v1.MoveOptionsH\x00R\x07options\x88\x01\x01\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extraB\n\n\x08_options"#\n!MoveThroughJointPositionsResponse"P\n\x0bStopRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12-\n\x05extra\x18c \x01(\x0b2\x17.google.protobuf.StructR\x05extra"\x0e\n\x0cStopResponse"\xae\x01\n\x06Status\x127\n\x0cend_position\x18\x01 \x01(\x0b2\x14.viam.common.v1.PoseR\x0bendPosition\x12N\n\x0fjoint_positions\x18\x02 \x01(\x0b2%.viam.component.arm.v1.JointPositionsR\x0ejointPositions\x12\x1b\n\tis_moving\x18\x03 \x01(\x08R\x08isMoving"%\n\x0fIsMovingRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name"/\n\x10IsMovingResponse\x12\x1b\n\tis_moving\x18\x01 \x01(\x08R\x08isMoving"\xac\x01\n\x0bMoveOptions\x123\n\x14max_vel_degs_per_sec\x18\x01 \x01(\x01H\x00R\x10maxVelDegsPerSec\x88\x01\x01\x125\n\x15max_acc_degs_per_sec2\x18\x02 \x01(\x01H\x01R\x11maxAccDegsPerSec2\x88\x01\x01B\x17\n\x15_max_vel_degs_per_secB\x18\n\x16_max_acc_degs_per_sec22\xc5\x0f\n\nArmService\x12\xa1\x01\n\x0eGetEndPosition\x12,.viam.component.arm.v1.GetEndPositionRequest\x1a-.viam.component.arm.v1.GetEndPositionResponse"2\x82\xd3\xe4\x93\x02,\x12*/viam/api/v1/component/arm/{name}/position\x12\xa5\x01\n\x0eMoveToPosition\x12,.viam.component.arm.v1.MoveToPositionRequest\x1a-.viam.component.arm.v1.MoveToPositionResponse"6\xa0\x92)\x01\x82\xd3\xe4\x93\x02,\x1a*/viam/api/v1/component/arm/{name}/position\x12\xb1\x01\n\x11GetJointPositions\x12/.viam.component.arm.v1.GetJointPositionsRequest\x1a0.viam.component.arm.v1.GetJointPositionsResponse"9\x82\xd3\xe4\x93\x023\x121/viam/api/v1/component/arm/{name}/joint_positions\x12\xc3\x01\n\x14StreamJointPositions\x122.viam.component.arm.v1.StreamJointPositionsRequest\x1a3.viam.component.arm.v1.StreamJointPositionsResponse"@\x82\xd3\xe4\x93\x02:\x128/viam/api/v1/component/arm/{name}/stream_joint_positions0\x01\x12\xbe\x01\n\x14MoveToJointPositions\x122.viam.component.arm.v1.MoveToJointPositionsRequest\x1a3.viam.component.arm.v1.MoveToJointPositionsResponse"=\xa0\x92)\x01\x82\xd3\xe4\x93\x023\x1a1/viam/api/v1/component/arm/{name}/joint_positions\x12\xda\x01\n\x19MoveThroughJointPositions\x127.viam.component.arm.v1.MoveThroughJointPositionsRequest\x1a8.viam.component.arm.v1.MoveThroughJointPositionsResponse"J\xa0\x92)\x01\x82\xd3\xe4\x93\x02@">/viam/api/v1/component/arm/{name}/move_through_joint_positions\x12\x7f\n\x04Stop\x12".viam.component.arm.v1.StopRequest\x1a#.viam.component.arm.v1.StopResponse".\x82\xd3\xe4\x93\x02("&/viam/api/v1/component/arm/{name}/stop\x12\x90\x01\n\x08IsMoving\x12&.viam.component.arm.v1.IsMovingRequest\x1a\'.viam.component.arm.v1.IsMovingResponse"3\x82\xd3\xe4\x93\x02-\x12+/viam/api/v1/component/arm/{name}/is_moving\x12\x86\x01\n\tDoCommand\x12 .viam.common.v1.DoCommandRequest\x1a!.viam.common.v1.DoCommandResponse"4\x82\xd3\xe4\x93\x02.",/viam/api/v1/component/arm/{name}/do_command\x12\x92\x01\n\rGetKinematics\x12$.viam.common.v1.GetKinematicsRequest\x1a%.viam.common.v1.GetKinematicsResponse"4\x82\xd3\xe4\x93\x02.\x12,/viam/api/v1/component/arm/{name}/kinematics\x12\x92\x01\n\rGetGeometries\x12$.viam.common.v1.GetGeometriesRequest\x1a%.viam.common.v1.GetGeometriesResponse"4\x82\xd3\xe4\x93\x02.\x12,/viam/api/v1/component/arm/{name}/geometries\x12\x8b\x01\n\x0bGet3DModels\x12".viam.common.v1.Get3DModelsRequest\x1a#.viam.common.v1.Get3DModelsResponse"3\x82\xd3\xe4\x93\x02-\x12+/viam/api/v1/component/arm/{name}/3d_modelsB=\n\x19com.viam.component.arm.v1Z go.viam.com/api/component/arm/v1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'component.arm.v1.arm_pb2', _globals) @@ -22,6 +23,8 @@ _globals['_ARMSERVICE'].methods_by_name['MoveToPosition']._serialized_options = b'\xa0\x92)\x01\x82\xd3\xe4\x93\x02,\x1a*/viam/api/v1/component/arm/{name}/position' _globals['_ARMSERVICE'].methods_by_name['GetJointPositions']._loaded_options = None _globals['_ARMSERVICE'].methods_by_name['GetJointPositions']._serialized_options = b'\x82\xd3\xe4\x93\x023\x121/viam/api/v1/component/arm/{name}/joint_positions' + _globals['_ARMSERVICE'].methods_by_name['StreamJointPositions']._loaded_options = None + _globals['_ARMSERVICE'].methods_by_name['StreamJointPositions']._serialized_options = b'\x82\xd3\xe4\x93\x02:\x128/viam/api/v1/component/arm/{name}/stream_joint_positions' _globals['_ARMSERVICE'].methods_by_name['MoveToJointPositions']._loaded_options = None _globals['_ARMSERVICE'].methods_by_name['MoveToJointPositions']._serialized_options = b'\xa0\x92)\x01\x82\xd3\xe4\x93\x023\x1a1/viam/api/v1/component/arm/{name}/joint_positions' _globals['_ARMSERVICE'].methods_by_name['MoveThroughJointPositions']._loaded_options = None @@ -38,39 +41,43 @@ _globals['_ARMSERVICE'].methods_by_name['GetGeometries']._serialized_options = b'\x82\xd3\xe4\x93\x02.\x12,/viam/api/v1/component/arm/{name}/geometries' _globals['_ARMSERVICE'].methods_by_name['Get3DModels']._loaded_options = None _globals['_ARMSERVICE'].methods_by_name['Get3DModels']._serialized_options = b'\x82\xd3\xe4\x93\x02-\x12+/viam/api/v1/component/arm/{name}/3d_models' - _globals['_GETENDPOSITIONREQUEST']._serialized_start = 137 - _globals['_GETENDPOSITIONREQUEST']._serialized_end = 227 - _globals['_GETENDPOSITIONRESPONSE']._serialized_start = 229 - _globals['_GETENDPOSITIONRESPONSE']._serialized_end = 295 - _globals['_JOINTPOSITIONS']._serialized_start = 297 - _globals['_JOINTPOSITIONS']._serialized_end = 337 - _globals['_GETJOINTPOSITIONSREQUEST']._serialized_start = 339 - _globals['_GETJOINTPOSITIONSREQUEST']._serialized_end = 432 - _globals['_GETJOINTPOSITIONSRESPONSE']._serialized_start = 434 - _globals['_GETJOINTPOSITIONSRESPONSE']._serialized_end = 530 - _globals['_MOVETOPOSITIONREQUEST']._serialized_start = 533 - _globals['_MOVETOPOSITIONREQUEST']._serialized_end = 661 - _globals['_MOVETOPOSITIONRESPONSE']._serialized_start = 663 - _globals['_MOVETOPOSITIONRESPONSE']._serialized_end = 687 - _globals['_MOVETOJOINTPOSITIONSREQUEST']._serialized_start = 690 - _globals['_MOVETOJOINTPOSITIONSREQUEST']._serialized_end = 855 - _globals['_MOVETOJOINTPOSITIONSRESPONSE']._serialized_start = 857 - _globals['_MOVETOJOINTPOSITIONSRESPONSE']._serialized_end = 887 - _globals['_MOVETHROUGHJOINTPOSITIONSREQUEST']._serialized_start = 890 - _globals['_MOVETHROUGHJOINTPOSITIONSREQUEST']._serialized_end = 1139 - _globals['_MOVETHROUGHJOINTPOSITIONSRESPONSE']._serialized_start = 1141 - _globals['_MOVETHROUGHJOINTPOSITIONSRESPONSE']._serialized_end = 1176 - _globals['_STOPREQUEST']._serialized_start = 1178 - _globals['_STOPREQUEST']._serialized_end = 1258 - _globals['_STOPRESPONSE']._serialized_start = 1260 - _globals['_STOPRESPONSE']._serialized_end = 1274 - _globals['_STATUS']._serialized_start = 1277 - _globals['_STATUS']._serialized_end = 1451 - _globals['_ISMOVINGREQUEST']._serialized_start = 1453 - _globals['_ISMOVINGREQUEST']._serialized_end = 1490 - _globals['_ISMOVINGRESPONSE']._serialized_start = 1492 - _globals['_ISMOVINGRESPONSE']._serialized_end = 1539 - _globals['_MOVEOPTIONS']._serialized_start = 1542 - _globals['_MOVEOPTIONS']._serialized_end = 1714 - _globals['_ARMSERVICE']._serialized_start = 1717 - _globals['_ARMSERVICE']._serialized_end = 3508 \ No newline at end of file + _globals['_GETENDPOSITIONREQUEST']._serialized_start = 170 + _globals['_GETENDPOSITIONREQUEST']._serialized_end = 260 + _globals['_GETENDPOSITIONRESPONSE']._serialized_start = 262 + _globals['_GETENDPOSITIONRESPONSE']._serialized_end = 328 + _globals['_JOINTPOSITIONS']._serialized_start = 330 + _globals['_JOINTPOSITIONS']._serialized_end = 370 + _globals['_GETJOINTPOSITIONSREQUEST']._serialized_start = 372 + _globals['_GETJOINTPOSITIONSREQUEST']._serialized_end = 465 + _globals['_GETJOINTPOSITIONSRESPONSE']._serialized_start = 467 + _globals['_GETJOINTPOSITIONSRESPONSE']._serialized_end = 563 + _globals['_STREAMJOINTPOSITIONSREQUEST']._serialized_start = 565 + _globals['_STREAMJOINTPOSITIONSREQUEST']._serialized_end = 692 + _globals['_STREAMJOINTPOSITIONSRESPONSE']._serialized_start = 695 + _globals['_STREAMJOINTPOSITIONSRESPONSE']._serialized_end = 880 + _globals['_MOVETOPOSITIONREQUEST']._serialized_start = 883 + _globals['_MOVETOPOSITIONREQUEST']._serialized_end = 1011 + _globals['_MOVETOPOSITIONRESPONSE']._serialized_start = 1013 + _globals['_MOVETOPOSITIONRESPONSE']._serialized_end = 1037 + _globals['_MOVETOJOINTPOSITIONSREQUEST']._serialized_start = 1040 + _globals['_MOVETOJOINTPOSITIONSREQUEST']._serialized_end = 1205 + _globals['_MOVETOJOINTPOSITIONSRESPONSE']._serialized_start = 1207 + _globals['_MOVETOJOINTPOSITIONSRESPONSE']._serialized_end = 1237 + _globals['_MOVETHROUGHJOINTPOSITIONSREQUEST']._serialized_start = 1240 + _globals['_MOVETHROUGHJOINTPOSITIONSREQUEST']._serialized_end = 1489 + _globals['_MOVETHROUGHJOINTPOSITIONSRESPONSE']._serialized_start = 1491 + _globals['_MOVETHROUGHJOINTPOSITIONSRESPONSE']._serialized_end = 1526 + _globals['_STOPREQUEST']._serialized_start = 1528 + _globals['_STOPREQUEST']._serialized_end = 1608 + _globals['_STOPRESPONSE']._serialized_start = 1610 + _globals['_STOPRESPONSE']._serialized_end = 1624 + _globals['_STATUS']._serialized_start = 1627 + _globals['_STATUS']._serialized_end = 1801 + _globals['_ISMOVINGREQUEST']._serialized_start = 1803 + _globals['_ISMOVINGREQUEST']._serialized_end = 1840 + _globals['_ISMOVINGRESPONSE']._serialized_start = 1842 + _globals['_ISMOVINGRESPONSE']._serialized_end = 1889 + _globals['_MOVEOPTIONS']._serialized_start = 1892 + _globals['_MOVEOPTIONS']._serialized_end = 2064 + _globals['_ARMSERVICE']._serialized_start = 2067 + _globals['_ARMSERVICE']._serialized_end = 4056 diff --git a/src/viam/gen/component/arm/v1/arm_pb2.pyi b/src/viam/gen/component/arm/v1/arm_pb2.pyi index b955f5847..ddd254255 100644 --- a/src/viam/gen/component/arm/v1/arm_pb2.pyi +++ b/src/viam/gen/component/arm/v1/arm_pb2.pyi @@ -9,6 +9,7 @@ import google.protobuf.descriptor import google.protobuf.internal.containers import google.protobuf.message import google.protobuf.struct_pb2 +import google.protobuf.timestamp_pb2 import typing DESCRIPTOR: google.protobuf.descriptor.FileDescriptor @@ -114,6 +115,61 @@ class GetJointPositionsResponse(google.protobuf.message.Message): ... global___GetJointPositionsResponse = GetJointPositionsResponse +@typing.final +class StreamJointPositionsRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + NAME_FIELD_NUMBER: builtins.int + FPS_FIELD_NUMBER: builtins.int + EXTRA_FIELD_NUMBER: builtins.int + name: builtins.str + 'Name of an arm' + fps: builtins.int + 'Target frames per second for the stream' + + @property + def extra(self) -> google.protobuf.struct_pb2.Struct: + """Additional arguments to the method""" + + def __init__(self, *, name: builtins.str=..., fps: builtins.int | None=..., extra: google.protobuf.struct_pb2.Struct | None=...) -> None: + ... + + def HasField(self, field_name: typing.Literal['_fps', b'_fps', 'extra', b'extra', 'fps', b'fps']) -> builtins.bool: + ... + + def ClearField(self, field_name: typing.Literal['_fps', b'_fps', 'extra', b'extra', 'fps', b'fps', 'name', b'name']) -> None: + ... + + def WhichOneof(self, oneof_group: typing.Literal['_fps', b'_fps']) -> typing.Literal['fps'] | None: + ... +global___StreamJointPositionsRequest = StreamJointPositionsRequest + +@typing.final +class StreamJointPositionsResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + POSITIONS_FIELD_NUMBER: builtins.int + TIMESTAMP_FIELD_NUMBER: builtins.int + SEQUENCE_FIELD_NUMBER: builtins.int + sequence: builtins.int + 'Sequential message number, used to detect dropped messages' + + @property + def positions(self) -> global___JointPositions: + """Current joint positions""" + + @property + def timestamp(self) -> google.protobuf.timestamp_pb2.Timestamp: + """Timestamp when positions were captured from the arm hardware""" + + def __init__(self, *, positions: global___JointPositions | None=..., timestamp: google.protobuf.timestamp_pb2.Timestamp | None=..., sequence: builtins.int=...) -> None: + ... + + def HasField(self, field_name: typing.Literal['positions', b'positions', 'timestamp', b'timestamp']) -> builtins.bool: + ... + + def ClearField(self, field_name: typing.Literal['positions', b'positions', 'sequence', b'sequence', 'timestamp', b'timestamp']) -> None: + ... +global___StreamJointPositionsResponse = StreamJointPositionsResponse + @typing.final class MoveToPositionRequest(google.protobuf.message.Message): """Moves an arm to the specified pose that is within the reference frame of the arm. diff --git a/src/viam/proto/component/arm/__init__.py b/src/viam/proto/component/arm/__init__.py index 65297b318..cb2317462 100644 --- a/src/viam/proto/component/arm/__init__.py +++ b/src/viam/proto/component/arm/__init__.py @@ -1,9 +1,12 @@ -""" +''' @generated by Viam. Do not edit manually! -""" - -from ....gen.component.arm.v1.arm_grpc import ArmServiceBase, ArmServiceStub, UnimplementedArmServiceBase +''' +from ....gen.component.arm.v1.arm_grpc import ( + ArmServiceBase, + ArmServiceStub, + UnimplementedArmServiceBase +) from ....gen.component.arm.v1.arm_pb2 import ( GetEndPositionRequest, GetEndPositionResponse, @@ -22,27 +25,31 @@ Status, StopRequest, StopResponse, + StreamJointPositionsRequest, + StreamJointPositionsResponse ) __all__ = [ - "ArmServiceBase", - "ArmServiceStub", - "UnimplementedArmServiceBase", - "GetEndPositionRequest", - "GetEndPositionResponse", - "GetJointPositionsRequest", - "GetJointPositionsResponse", - "IsMovingRequest", - "IsMovingResponse", - "JointPositions", - "MoveOptions", - "MoveThroughJointPositionsRequest", - "MoveThroughJointPositionsResponse", - "MoveToJointPositionsRequest", - "MoveToJointPositionsResponse", - "MoveToPositionRequest", - "MoveToPositionResponse", - "Status", - "StopRequest", - "StopResponse", + 'ArmServiceBase', + 'ArmServiceStub', + 'UnimplementedArmServiceBase', + 'GetEndPositionRequest', + 'GetEndPositionResponse', + 'GetJointPositionsRequest', + 'GetJointPositionsResponse', + 'IsMovingRequest', + 'IsMovingResponse', + 'JointPositions', + 'MoveOptions', + 'MoveThroughJointPositionsRequest', + 'MoveThroughJointPositionsResponse', + 'MoveToJointPositionsRequest', + 'MoveToJointPositionsResponse', + 'MoveToPositionRequest', + 'MoveToPositionResponse', + 'Status', + 'StopRequest', + 'StopResponse', + 'StreamJointPositionsRequest', + 'StreamJointPositionsResponse', ] diff --git a/tests/mocks/components.py b/tests/mocks/components.py index e435db614..a795e7afa 100644 --- a/tests/mocks/components.py +++ b/tests/mocks/components.py @@ -90,6 +90,22 @@ async def move_to_joint_positions( self.extra = extra self.timeout = timeout + async def stream_joint_positions( + self, + *, + fps: Optional[int] = None, + extra: Optional[Dict[str, Any]] = None, + timeout: Optional[float] = None, + **kwargs, + ): + async def read() -> AsyncIterator[JointPositions]: + for _ in range(5): + yield JointPositions(values=[choice([-1, 0, 1]) for _ in self.joint_positions.values]) + + self.extra = extra + self.timeout = timeout + return StreamWithIterator(read()) + async def stop(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs): self.is_stopped = True self.extra = extra diff --git a/tests/test_robot.py b/tests/test_robot.py index e7e8097c8..4bb4ce6e2 100644 --- a/tests/test_robot.py +++ b/tests/test_robot.py @@ -52,6 +52,7 @@ from viam.robot.client import RobotClient from viam.robot.service import RobotService from viam.services.mlmodel.client import MLModelClient +from viam.streams import Stream as ViamStream from viam.utils import dict_to_struct from .mocks.components import MockArm, MockCamera, MockMotor, MockMovementSensor, MockSensor @@ -489,6 +490,16 @@ async def get_joint_positions( ) -> JointPositions: return await self.actual_client.get_joint_positions(extra=extra, timeout=timeout) + async def stream_joint_positions( + self, + *, + fps: Optional[int] = None, + extra: Optional[Dict[str, Any]] = None, + timeout: Optional[float] = None, + **kwargs, + ) -> ViamStream[JointPositions]: + return await self.actual_client.stream_joint_positions(fps=fps, extra=extra, timeout=timeout, **kwargs) + async def stop( self, *,