diff --git a/rclpy/rclpy/event_handler.py b/rclpy/rclpy/event_handler.py index caad8f502..9bcd111c8 100644 --- a/rclpy/rclpy/event_handler.py +++ b/rclpy/rclpy/event_handler.py @@ -137,7 +137,7 @@ def __exit__(self, t, v, tb): """Mark event as not-in-use to allow destruction after waiting on it.""" self.__event.__exit__(t, v, tb) - def destroy(self): + def destroy(self) -> None: self.__event.destroy_when_not_in_use() diff --git a/rclpy/rclpy/node.py b/rclpy/rclpy/node.py index d4b128f34..66c669ebb 100644 --- a/rclpy/rclpy/node.py +++ b/rclpy/rclpy/node.py @@ -113,6 +113,10 @@ NodeNameNonExistentError = _rclpy.NodeNameNonExistentError +class NodeHandle: + pass + + class Node: """ A Node in the ROS graph. @@ -1534,7 +1538,7 @@ def create_publisher( callback_group: Optional[CallbackGroup] = None, event_callbacks: Optional[PublisherEventCallbacks] = None, qos_overriding_options: Optional[QoSOverridingOptions] = None, - publisher_class: Type[Publisher] = Publisher, + publisher_class: Type[Publisher[MsgT]] = Publisher[MsgT], ) -> Publisher[MsgT]: """ Create a new publisher. diff --git a/rclpy/rclpy/publisher.py b/rclpy/rclpy/publisher.py index 998e555d9..7a47ab5dc 100644 --- a/rclpy/rclpy/publisher.py +++ b/rclpy/rclpy/publisher.py @@ -13,10 +13,11 @@ # limitations under the License. from types import TracebackType -from typing import Generic, List, Optional, Type, TypeVar, Union +from typing import Generic, List, Optional, Protocol, Type, TYPE_CHECKING, TypeVar, Union from rclpy.callback_groups import CallbackGroup -from rclpy.duration import Duration +from rclpy.destroyable import DestroyableType +from rclpy.duration import Duration, DurationType from rclpy.event_handler import EventHandler, PublisherEventCallbacks from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy from rclpy.qos import QoSProfile @@ -25,12 +26,43 @@ # Left to support Legacy TypeVars. MsgType = TypeVar('MsgType') +if TYPE_CHECKING: + from rclpy.node import NodeHandle + + +class PublisherHandle(DestroyableType, Protocol[MsgT]): + + def __init__(self, arg0: 'NodeHandle', arg1: Type[MsgT], arg2: str, arg3: object) -> None: + """Create PublisherHandle.""" + + @property + def pointer(self) -> int: + """Get the address of the entity as an integer.""" + + def get_logger_name(self) -> str: + """Get the name of the logger associated with the node of the publisher.""" + + def get_subscription_count(self) -> int: + """Count subscribers from a publisher.""" + + def get_topic_name(self) -> str: + """Retrieve the topic name from a Publisher.""" + + def publish(self, arg0: MsgT) -> None: + """Publish a message.""" + + def publish_raw(self, arg0: bytes) -> None: + """Publish a serialized message.""" + + def wait_for_all_acked(self, arg0: DurationType) -> bool: + """Wait until all published message data is acknowledged.""" + class Publisher(Generic[MsgT]): def __init__( self, - publisher_impl: _rclpy.Publisher, + publisher_impl: PublisherHandle[MsgT], msg_type: Type[MsgT], topic: str, qos_profile: QoSProfile, @@ -86,10 +118,10 @@ def topic_name(self) -> str: return self.__publisher.get_topic_name() @property - def handle(self): + def handle(self) -> PublisherHandle[MsgT]: return self.__publisher - def destroy(self): + def destroy(self) -> None: """ Destroy a container for a ROS publisher. @@ -130,7 +162,7 @@ def wait_for_all_acked(self, timeout: Duration = Duration(seconds=-1)) -> bool: with self.handle: return self.__publisher.wait_for_all_acked(timeout._duration_handle) - def __enter__(self) -> 'Publisher': + def __enter__(self) -> 'Publisher[MsgT]': return self def __exit__( diff --git a/rclpy/rclpy/type_support.py b/rclpy/rclpy/type_support.py index f13ad8415..33ef95d9d 100644 --- a/rclpy/rclpy/type_support.py +++ b/rclpy/rclpy/type_support.py @@ -52,7 +52,7 @@ class Msg(Protocol, metaclass=MsgMetaClass): pass -MsgT = TypeVar('MsgT', bound=Msg) +MsgT = TypeVar('MsgT', bound=Msg, contravariant=True) SrvRequestT = TypeVar('SrvRequestT', bound=Msg) SrvResponseT = TypeVar('SrvResponseT', bound=Msg)