rosros


Namerosros JSON
Version 0.2.6 PyPI version JSON
download
home_pagehttps://github.com/suurjaak/rosros
SummarySimple unified interface to ROS1 / ROS2 Python API
upload_time2024-11-20 19:16:07
maintainerNone
docs_urlNone
authorErki Suurjaak
requires_python>=3
licenseBSD
keywords ros ros1 ros2
VCS
bugtrack_url
requirements pyyaml
Travis-CI No Travis.
coveralls test coverage No coveralls.
            rosros
======

Simple unified interface to ROS1 / ROS2 Python API.

Auto-detects ROS version from run-time environment, and provides
equivalent functionality in either ROS version.

Main use-cases:
- helper library for working with ROS messages and data types
- replacement for ROS1 / ROS2 Python API
- drop-in replacement for `rospy` to convert a ROS1 package to a ROS1/ROS2 package
- drop-in replacement for `rclpy` to convert a ROS2 package to a ROS1/ROS2 package


Requires the corresponding ROS Python libraries: `rospy` family for ROS1,
`rclpy` for ROS2.

Full API documentation available at https://suurjaak.github.io/rosros.

- Example usage
  - Parameters
  - Constructor args in publishers and services
  - Timers
  - Bags
- ROS core functionality
  - Patches in ROS1
  - Patches in ROS2
- API helpers
- Converting an existing package
  - Existing ROS1 package to ROS1/ROS2 package
  - Existing ROS2 package to ROS1/ROS2 package
- Running tests
- Dependencies
- Attribution
- License


Example usage
-------------

A simple node that publishes a message on receiving a service request:

```python
import rosros

def on_trigger(req):
    pub.publish(True)
    return {"success": True, "message": "Triggered!"}

rosros.init_node("mynode")
params = rosros.init_params(service="/trigger", topic="/triggerings")
srv = rosros.create_service(params["service"], "std_srvs/Trigger", on_trigger)
pub = rosros.create_publisher(params["topic"], "std_msgs/Bool", latch=True, queue_size=2)
rosros.spin()
```

Equivalent ROS1 code using rospy:

```python
import rospy, std_msgs.msg, std_srvs.srv

def on_trigger(req):
    pub.publish(True)
    return {"success": True, "message": "Triggered!"}

rospy.init_node("mynode")
service = rospy.get_param("~service", "/trigger")
topic   = rospy.get_param("~topic",   "/triggerings")
rospy.set_param("~service", service)
rospy.set_param("~topic",   topic)
srv = rospy.Service(service, std_srvs.srv.Trigger, on_trigger)
pub = rospy.Publisher(topic, std_msgs.msg.Bool, latch=True, queue_size=2)
rospy.spin()
```

Equivalent ROS2 code using rclpy:

```python
import rclpy, rclpy.qos, std_msgs.msg, std_srvs.srv

def on_trigger(req, resp):
    pub.publish(std_msgs.msg.Bool(data=True))
    resp.success = True
    resp.message = "Triggered!"
    return resp

rclpy.init()
node = rclpy.create_node("mynode")
service = node.get_parameter_or("service", rclpy.Parameter("", value="/trigger")).value
topic   = node.get_parameter_or("topic",   rclpy.Parameter("", value="/triggerings")).value
if not node.has_parameter("service"):
    node.declare_parameter("service", service)
if not node.has_parameter("topic"):
    node.declare_parameter("topic", topic)
srv = node.create_service(std_srvs.srv.Trigger, service, on_trigger)
qos = rclpy.qos.QoSProfile(depth=2, durability=rclpy.qos.DurabilityPolicy.TRANSIENT_LOCAL)
pub = node.create_publisher(std_msgs.msg.Bool, topic, qos)
rclpy.spin(node)
```

Any ROS2-only extras used will be ignored under ROS1, like topic QoS reliability.


### Parameters

rosros provides a convenient way to auto-load all node parameters,
and override or populate additional parameters from a nested dictionary.

```python
rosros.init_node("mynode")
# If the given defaults were not among existing node parameters,
# they will be auto-declared on the node.
params = rosros.init_params({"my": {"nested": {"param1": 5, "param2": True}}})
```


### Constructor args in publishers and services

rosros provides short-hand convenience wrappers for publishing messages,
invoking services and returning service responses, without needing to
create the message or service request/response class instance explicitly.

ROS1 already provides this short-hand in `rospy`,
but ROS2 API requires the instances to be created explicitly.

rosros also supports giving message or service request as dictionary.

In rosros:

```python
pub = rosros.create_publisher("/topic", "std_msgs/Bool")
pub.publish(True)
pub.publish(data=True)
pub.publish({"data": True})
pub.publish()

# Service callback need not accept any parameters if request object is irrelevant
srv = rosros.create_service("/service", "std_srvs/SetBool", lambda: {"message": "Triggered!"})

cli = rosros.create_client("/service", "std_srvs/SetBool")
cli.call(True)
cli.call(data=True)
cli.call({"data": True})
cli(True)
```

Equivalent ROS1 code using rospy:

```python
pub = rospy.Publisher("/topic", std_msgs.msg.Bool, queue_size=0)
pub.publish(True)
pub.publish(data=True)
pub.publish(std_msgs.msg.Bool(True))
pub.publish()

srv = rospy.Service("/service", std_srvs.srv.SetBool, lambda req: {"message": "Triggered!"})

cli = rospy.ServiceProxy("/service", std_srvs.srv.SetBool)
cli.call(True)
cli.call(data=True)
cli.call(std_srvs.srv.SetBoolRequest(True))
cli(True)
```

Equivalent ROS2 code using rospy:

```python
pub = mynode.create_publisher(std_msgs.msg.Bool, "/topic", 0)
pub.publish(std_msgs.msg.Bool(data=True))
pub.publish(std_msgs.msg.Bool(data=True))
pub.publish(std_msgs.msg.Bool(data=True))
pub.publish(std_msgs.msg.Bool())

def callback(req, resp):
  resp.message = "Triggered!"
  return resp
srv = mynode.create_service(std_srvs.srv.SetBool, "/service", callback)

cli = mynode.create_client(std_srvs.srv.SetBool, "/service")
cli.call(std_srvs.srv.SetBool.Request(data=True))
```

### Timers

rosros provides convenience one-shot timers in ROS2 like ROS1 does.

Additionally, rosros provides an option to start invoking the timer callback
immediately, instead of waiting one period before first call.

```python
timer = rosros.create_timer(2, lambda: print("just once, after 2 sec"), oneshot=True)

timer = rosros.create_timer(5, lambda: print("every 5 sec, starting now"), immediate=True)

# Can take seconds, can take an explicit Duration object
timer = rosros.create_timer(rosros.api.make_duration(5), lambda: print("every 5 sec"))
```

### Bags

rosros provides a unified interface for reading and writing ROS bags.

Bag format is the custom ROS format in ROS1, and the SQLite database format in ROS2.

```python
bag = rosros.Bag("my.bag", mode="a")
print(bag)  # Prints bag metainfo, equivalent to "rosbag info"

bag.write("/my/topic", std_msgs.msg.Bool())

for topic, message, stamp in bag:
    print("[%s] %s:  %s" % (rosros.api.to_sec(stamp), topic, message))
```




ROS core functionality
----------------------

Functionality for creating and operating a ROS node.

Objects returned are standard `rospy` / `rclpy` API objects, patched with
additional members for a unified interface _**conforming to ROS1 API**_.


| Name                                | Description                                                                                | Arguments
| ----------------------------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------
|                                     | **Startup, spin and shutdown**                                                             | |
| `rosros.init_node`                  | initializes ROS and creates ROS node (and returns Node object in ROS2)                     | `name, args=None, namespace=None, anonymous=True, log_level=None, enable_rosout=True, multithreaded=True, reentrant=False`
| `rosros.start_spin`                 | sets ROS node spinning forever in a background thread                                      | |
| `rosros.spin`                       | spins ROS node forever                                                                     | |
| `rosros.spin_once`                  | waits until timeout in ROS1; executes one ROS operation or waits until timeout in ROS2     | `timeout=None`
| `rosros.spin_until_future_complete` | spins ROS until future complete or timeout reached or ROS shut down                        | `future, timeout=None`
| `rosros.ok`                         | returns whether ROS has been initialized and is not shut down                              | |
| `rosros.on_shutdown`                | registers function to be called on shutdown                                                | `callback, *args, **kwargs`
| `rosros.shutdown`                   | shuts down live ROS node, if any                                                           | `reason=None`
|                                     |                                                                                            | |
|                                     | **Parameters**                                                                             | |
| `rosros.init_params`                | sets all parameters on node from defaults dictionary, returns full parameters dictionary.  | `defaults=None, **defaultkws`
|                                     | In ROS2, auto-declares unregistered parameters.                                            | |
| `rosros.has_param`                  | returns whether the parameter exists                                                       | `name`
| `rosros.get_param`                  | returns parameter value from current ROS node                                              | `name, default=None, autoset=True`
| `rosros.get_param_names`            | returns the names of all current ROS node parameters                                       | |
| `rosros.get_params`                 | returns the current ROS node parameters, by default as nested dictionary                   | `nested=True`
| `rosros.set_param`                  | sets a parameter on the node.                                                              | `name, value, descriptor=None`
|                                     | In ROS2, parameter will be auto-declared if unknown so far.                                | |
| `rosros.delete_param`               | deletes parameter from the node                                                            | `name`
|                                     |                                                                                            | |
|                                     | **Topics, services, timers and rates**                                                     | |
| `rosros.create_publisher`           | returns a ROS publisher instance                                                           | `topic, cls_or_typename, latch=False, queue_size=0, **qosargs`
| `rosros.create_subscriber`          | returns a ROS subscriber instance                                                          | `topic, cls_or_typename, callback, callback_args=None, queue_size=0, raw=False, **qosargs`
| `rosros.create_service`             | returns a ROS service server instance, for providing a service                             | `service, cls_or_typename, callback, **qosargs`
| `rosros.create_client`              | returns a ROS service client instance, for invoking a service                              | `service, cls_or_typename, **qosargs`
| `rosros.create_timer`               | returns a ROS timer instance                                                               | `period, callback, oneshot=False, immediate=False`
| `rosros.create_rate`                | returns a ROS rate instance, for sleeping at a fixed rate                                  | `frequency`
| `rosros.destroy_entity`             | closes the given publisher, subscriber, service client, service server, or timer instance  | `item`
| `rosros.AnyMsg`                     | `rospy.AnyMsg` in ROS1, stand-in class with equivalent functionality in ROS2               | |
| `rosros.sleep`                      | sleeps for the specified duration in ROS time                                              | `duration`
| `rosros.wait_for_publisher`         | blocks until topic has at least one publisher                                              | `topic, timeout=None, cls_or_typename=None`
| `rosros.wait_for_subscriber`        | blocks until topic has at least one subscriber                                             | `topic, timeout=None, cls_or_typename=None`
| `rosros.wait_for_service`           | blocks until service is available                                                          | `service, timeout=None, cls_or_typename=None`
|                                     |                                                                                            | |
|                                     | **ROS bags**                                                                               | |
| `rosros.Bag`                        | ROS bag reader and writer;                                                                 | |
|                                     | `rosbag.Bag` in ROS1 with some additional functionality;                                   | |
|                                     | equivalent class in ROS2, using the ROS2 default SQLite format                             | |
|                                     |                                                                                            | |
|                                     | **Information queries**                                                                    | |
| `rosros.get_namespace`              | returns ROS node namespace                                                                 | |
| `rosros.get_node_name`              | returns ROS node full name with namespace                                                  | |
| `rosros.get_nodes`                  | returns all ROS nodes, as `[node full name, ]`                                             | |
| `rosros.get_topics`                 | returns all available ROS topics, as `[(topic name, [type name, ]), ]`                     | |
| `rosros.get_services`               | returns all available ROS services, as `[(service name, [type name, ]), ]`                 | |
| `rosros.remap_name`                 | returns the absolute remapped topic/service name if mapping exists                         | `name, namespace=None`
| `rosros.resolve_name`               | returns absolute remapped name, namespaced under current node if relative or private       | `name, namespace=None`
|                                     |                                                                                            | |
|                                     | **Miscellaneous**                                                                          | |
| `rosros.get_logger`                 | returns `logging.Logger` for logging to ROS log handler;                                   | |
|                                     | logger supports additional keywords `__once__`, `__throttle__`, `__throttle_identical__`   | |
| `rosros.get_ros_version`            | returns ROS version information as a data dictionary                                       | |
| `rosros.get_rostime`                | returns ROS time instance for current ROS clock time                                       | |
| `rosros.register_init`              | informs `rosros` of ROS having been initialized outside of `init_node()`.                  | `node=None`
|                                     | Giving node as argument is mandatory in ROS2.                                              | |


### Patches in ROS1

Additional functionality patched onto `rospy` classes for convenience:

- `rospy.ServiceProxy.call_async(*args, **kwargs):`
  makes service call in a background thread, returns `concurrent.futures.Future`-like response
- `rospy.ServiceProxy.wait_for_service(timeout=None, timeout_sec=None):`
  waits for service to become available, returns `True`, or `False` on timeout
- `rospy.ServiceProxy.service_is_ready():`
   returns whether service is currently available
- `rospy.Subscriber:`
  callbacks are invoked with serialized message bytes if subscription was created with `raw=True`
- `rosbag.Bag.get_message_definition(msg_or_type):`
   returns ROS1 message type definition full text from bag
- `rosbag.Bag.get_message_class(typename, typehash=None):`
   returns ROS1 message class
- `rosbag.Bag.get_message_type_hash(msg_or_type):`
   returns ROS1 message type MD5 hash
- `rosbag.Bag.get_topic_info():`
  returns topic and message type metainfo as `{(topic, typename, typehash): count}`
- `rosbag.Bag.reindex_file(f):`
  reindexes bag file on disk; makes a temporary copy in file directory


### Patches in ROS2

The following classes are patched with full conformity to their equivalents in ROS:

- `rclpy.client.Client`
- `rclpy.publisher.Publisher`
- `rclpy.service.Service`
- `rclpy.subscription.Subscription`
- `rclpy.duration.Duration`
- `rclpy.time.Time`
- `rclpy.timer.Rate`
- `rclpy.timer.Timer`

E.g. durations support `+-*/` arithmetic, and publisher / subscriber provide `.md5sum` for message type hash.


API helpers
-----------

Functionality for working with message types and data and metainfo.
Can be used as stand-alone library functions without initializing rosros core.

| Name                                    | Description                                                                                   | Arguments
| --------------------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------
|                                         | **Message types and instances**                                                               | |
| `rosros.api.get_message_class`          | returns ROS message / service class object, like `std_msgs.msg.Bool` for `"std_msgs/Bool"`    | `msg_or_type`
| `rosros.api.get_message_definition`     | returns ROS message or service request/response type definition text,                         | `msg_or_type, full=True`
|                                         | by default including message subtype definitions                                              | |
| `rosros.api.get_message_fields`         | returns `{field name: field type name}` if ROS message or service request/response, else `{}` | `val`
| `rosros.api.get_message_header`         | returns message `Header`-attribute if any, else `None`                                        | `val`
| `rosros.api.get_message_type`           | returns ROS message / service canonical type name, like `"std_msgs/Header"`                   | `msg_or_cls`
|                                         | or `"*"` for `AnyMsg`                                                                         | |
| `rosros.api.get_message_type_hash`      | returns ROS message / service type MD5 hash                                                   | `msg_or_type`
| `rosros.api.get_message_value`          | returns message attribute value, with numeric arrays converted to lists                       | `msg, name, default=...`
| `rosros.api.is_ros_message`             | returns whether value is a ROS message or service request/response class or instance          | `val`
|                                         |                                                                                               | |
|                                         | **Message conversion**                                                                        | |
| `rosros.api.deserialize_message`        | returns ROS message or service request/response instantiated from serialized binary           | `raw, cls_or_typename`
| `rosros.api.dict_to_message`            | returns ROS message populated from Python dictionary                                          | `dct, msg_or_type`
| `rosros.api.message_to_dict`            | returns ROS message as nested Python dictionary;                                              | `msg, replace=None`
|                                         | with optional replacements for certain values like `{math.nan: None}`                         | |
| `rosros.api.message_to_str`             | returns ROS message as an evaluatable string, e.g. `"std_msgs.msg.UInt8(data=0)"`             | `msg, indent=None`
| `rosros.api.serialize_message`          | returns ROS message or service request/response as a serialized binary of `bytes()`           | `msg`
|                                         |                                                                                               | |
|                                         | **Service classes and definitions**                                                           | |
| `rosros.api.get_service_definition`     | returns ROS service type definition text                                                      | `srv_or_type`
| `rosros.api.get_service_request_class`  | returns ROS service request class object                                                      | `srv_or_type`
| `rosros.api.get_service_response_class` | returns ROS service response class object                                                     | `srv_or_type`
| `rosros.api.is_ros_service`             | returns whether value is a ROS service class object                                           | `val`
|                                         |                                                                                               | |
|                                         | **Time and duration**                                                                         | |
| `rosros.api.is_ros_time`                | returns whether value is a ROS time/duration                                                  | `val`
| `rosros.api.make_duration`              | returns a ROS duration                                                                        | `secs=0, nsecs=0`
| `rosros.api.make_time`                  | returns a ROS time                                                                            | `secs=0, nsecs=0`
| `rosros.api.time_category`              | returns "time" or "duration" for time/duration type, else value                               | `msg_or_type`
| `rosros.api.time_message`               | returns ROS2 time/duration from `rclpy` as `builtin_interfaces` or vice versa,                | `val, to_message=True, clock_type=None`
|                                         | or value if not convertible                                                                   | |
| `rosros.api.to_datetime`                | returns value as `datetime.datetime` if value is ROS time/duration, else value                | `val`
| `rosros.api.to_decimal`                 | returns value as `decimal.Decimal` if value is ROS time/duration, else value                  | `val`
| `rosros.api.to_duration`                | returns value as ROS duration if convertible (int/float/time/datetime/decimal), else value    | `val`
| `rosros.api.to_nsec`                    | returns value in nanoseconds if value is ROS time/duration, else value                        | `val`
| `rosros.api.to_sec`                     | returns value in seconds if value is ROS time/duration, else value                            | `val`
| `rosros.api.to_sec_nsec`                | returns value in (seconds, nanoseconds) if value is ROS time/duration, else value             | `val`
| `rosros.api.to_time`                    | returns value as ROS time if convertible (int/float/duration/datetime/decimal), else value    | `val`
|                                         |                                                                                               | |
|                                         | **Class and type names**                                                                      | |
| `rosros.api.canonical`                  | returns `"pkg/Type"` for `"pkg/subdir/Type"`                                                  | `typename`
| `rosros.api.get_alias_type`             | returns ROS built-in type for alias like `"char"`, if any; reverse of `get_type_alias()`.     | `typename`
|                                         | In ROS1, `byte` and `char` are aliases for `int8` and `uint8`; in ROS2 the reverse.           | |
| `rosros.api.get_type_alias`             | returns alias like `"char"` for ROS built-in type, if any; reverse of `get_alias_type()`.     | `typename`
|                                         | In ROS1, `byte` and `char` are aliases for `int8` and `uint8`; in ROS2 the reverse.           | |
| `rosros.api.make_full_typename`         | returns `"pkg/msg/Type"` or `"pkg/srv/Type"` for `"pkg/Type"`                                 | `typename, category="msg"`
| `rosros.api.scalar`                     | returns scalar type from ROS message data type, like `"uint8"` from `uint8`-array;            | `typename`
|                                         | in ROS2, returns unbounded type, e.g. `"string"` from `"string<=10[<=5]"`.                    | |
|                                         |                                                                                               | |
|                                         | **Miscellaneous**                                                                             | |
| `rosros.api.format_param_name`          | returns parameter name with correct separator for ROS version, and leading sigils stripped    | `name`
| `rosros.api.PARAM_SEPARATOR`            | separator char between ROS parameter namespace parts                                          | |
| `rosros.api.ROS_BUILTIN_TYPES`          | list of ROS built-in numeric and string type names, like `["string", "uint8", ..]`            | |
| `rosros.api.ROS_NUMERIC_TYPES`          | list of ROS built-in numeric type names, like `["bool", "int8", ..]`                          | |
| `rosros.api.ROS_STRING_TYPES`           | list of ROS built-in string type names                                                        | |
| `rosros.api.ROS_TIME_CLASSES`           | ROS time/duration types mapped to type names                                                  | |
| `rosros.api.ROS_TIME_TYPES`             | list of ROS built-in time/duration type names                                                 | |


Converting an existing package
------------------------------

### Existing ROS1 package to ROS1/ROS2 package

rosros can be used as a (mostly) drop-in replacement for rospy
to make a ROS1 package equally usable under ROS2 - if the code relies only
on `rospy`, and does not go too deeply into using ROS1 specifics like `rosgraph`.

`rospy` imports can be replaced with `rosros.rospify`, e.g.

```python
import threading
from rosros import rospify as rospy
from std_msgs.msg import String

rospy.init_node("talker", anonymous=True)
pub = rospy.Publisher("chatter", String, queue_size=10)
rate = rospy.Rate(10)  # 10hz
threading.Thread(target=rospy.spin).start()  # Concession for ROS2: spin is always needed
while not rospy.is_shutdown():
    hello_str = "hello world %s" % rospy.get_time()
    rospy.loginfo(hello_str)
    pub.publish(hello_str)
    rate.sleep()
```

In ROS1 it provides `rospy`, and in ROS2 it provides matching API classes and functions.

Main behavioral differences of `rospify` in ROS2 vs ROS1:
- `Subscriber.get_num_connections()` always returns 0, as ROS2 does not provide this information
- subscribing with `AnyMsg` needs the publisher existing
- parameter operations always apply to node parameters, as ROS2 has no global parameters
- functions registered in `on_shutdown()` are called after node shutdown, not before
- `rosros.rospify.get_master()` requires local node to be initialized, unlike `rospy.get_master()`
- `rosros.rospify.SubscribeListener` does nothing


### Existing ROS2 package to ROS1/ROS2 package

rosros can also be used as a (mostly) drop-in replacement for rclpy
to make a ROS2 package equally usable under ROS1 - if the code mainly relies on
`rclpy` module and does not go deep into using ROS2 specifics like `builtin_interfaces`.

`rclpy` imports can be replaced with `rosros.rclify`, e.g.

```python
from rosros import rclify as rclpy
from std_msgs.msg import String

class MinimalPublisher(rclpy.node.Node):
    def __init__(self):
        super().__init__("minimal_publisher")
        self.publisher_ = self.create_publisher(String, "chatter", 10)
        timer_period = 0.1  # seconds
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.i = 0
    def timer_callback(self):
        msg = String()
        msg.data = "Hello World: %d" % self.i
        self.publisher_.publish(msg)
        self.get_logger().info('Publishing: "%s"' % msg.data)
        self.i += 1

rclpy.init()
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
```

In ROS2, `rclify` provides `rclpy`, and in ROS1 it provides
matching API modules and classes and functions.

Covers all the parts of `rclpy.node` that are supportable in ROS1, focused on:
creating nodes, publishing and subscribing topics, providing and invoking services,
working with rates and timers.

Main behavioral differences of `rclify` in ROS1 vs ROS2:
- one single node as always in ROS1
- QoS profiles ignore all options except `depth` (taken as `queue_size`),
  and `durability` equaling `TRANSIENT_LOCAL` (taken as `latch=True`)
- no contexts, no executors, no waitables
- no callback groups, no guard conditions, no middleware events

Methods pertaining to unsupported functionality do nothing and return `None`.
Unsupported arguments in methods are ignored.


Installation
------------

Developed and tested under ROS1 Noetic and ROS2 Foxy,
works in later ROS2 versions (tested up to ROS2 Jazzy). 

### Using pip

    pip install rosros

This will make the `rosros` library available to Python packages.

Requires ROS Python packages
(ROS1: rospy, roslib, rosbag, rosservice, genpy;
 ROS2: rclpy, rosidl_parser, rosidl_runtime_py, ament_index_python, builtin_interfaces).


### Using catkin

In a ROS1 workspace, under the source directory:

    git clone https://github.com/suurjaak/rosros.git
    cd rosros
    catkin build --this

This will make the `rosros` library available in the ROS1 workspace.


### Using colcon

In a ROS2 workspace, at the workspace root:

    git clone https://github.com/suurjaak/rosros.git src/rosros
    colcon build --packages-select rosros

This will make the `rosros` library available in the ROS2 workspace.


Running tests
-------------

ROS1:

    catkin test rosros

ROS2:

    colcon test --packages-select rosros

To run ROS2 tests with full log output:

    colcon test --packages-select rosros --event-handlers console_direct+ \
                --pytest-args "--capture=tee-sys"

Using pytest, either ROS1 or ROS2 (roscore needs to be running if ROS1):

    cd rosros
    pytest


Dependencies
------------

- pyyaml (https://pypi.org/project/PyYAML)

ROS1:
- genpy
- rosbag
- roslib
- rospy
- rosservice

ROS2:
- ament_index_python
- builtin_interfaces
- rclpy
- rosidl_parser
- rosidl_runtime_py

ROS2 test dependencies:
- pytest (https://pypi.org/project/pytest)
- pytest-forked (https://pypi.org/project/pytest-forked)

For generating API documentation:

- doxypypy (https://pypi.org/project/doxypypy;
            needs latest master: `pip install git+https://github.com/Feneric/doxypypy`)


Attribution
-----------

This package includes partially copied and modified code from `rclpy` core library,
released under the Apache-2.0 license.
See [doc/3rd-party licenses.txt](doc/3rd-party%20licenses.txt) for full details.


License
-------

Copyright (c) 2022 by Erki Suurjaak.
Released as free open source software under the BSD License,
see LICENSE.md for full details.



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/suurjaak/rosros",
    "name": "rosros",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3",
    "maintainer_email": null,
    "keywords": "ROS ROS1 ROS2",
    "author": "Erki Suurjaak",
    "author_email": "erki@lap.ee",
    "download_url": null,
    "platform": "any",
    "description": "rosros\n======\n\nSimple unified interface to ROS1 / ROS2 Python API.\n\nAuto-detects ROS version from run-time environment, and provides\nequivalent functionality in either ROS version.\n\nMain use-cases:\n- helper library for working with ROS messages and data types\n- replacement for ROS1 / ROS2 Python API\n- drop-in replacement for `rospy` to convert a ROS1 package to a ROS1/ROS2 package\n- drop-in replacement for `rclpy` to convert a ROS2 package to a ROS1/ROS2 package\n\n\nRequires the corresponding ROS Python libraries: `rospy` family for ROS1,\n`rclpy` for ROS2.\n\nFull API documentation available at https://suurjaak.github.io/rosros.\n\n- Example usage\n  - Parameters\n  - Constructor args in publishers and services\n  - Timers\n  - Bags\n- ROS core functionality\n  - Patches in ROS1\n  - Patches in ROS2\n- API helpers\n- Converting an existing package\n  - Existing ROS1 package to ROS1/ROS2 package\n  - Existing ROS2 package to ROS1/ROS2 package\n- Running tests\n- Dependencies\n- Attribution\n- License\n\n\nExample usage\n-------------\n\nA simple node that publishes a message on receiving a service request:\n\n```python\nimport rosros\n\ndef on_trigger(req):\n    pub.publish(True)\n    return {\"success\": True, \"message\": \"Triggered!\"}\n\nrosros.init_node(\"mynode\")\nparams = rosros.init_params(service=\"/trigger\", topic=\"/triggerings\")\nsrv = rosros.create_service(params[\"service\"], \"std_srvs/Trigger\", on_trigger)\npub = rosros.create_publisher(params[\"topic\"], \"std_msgs/Bool\", latch=True, queue_size=2)\nrosros.spin()\n```\n\nEquivalent ROS1 code using rospy:\n\n```python\nimport rospy, std_msgs.msg, std_srvs.srv\n\ndef on_trigger(req):\n    pub.publish(True)\n    return {\"success\": True, \"message\": \"Triggered!\"}\n\nrospy.init_node(\"mynode\")\nservice = rospy.get_param(\"~service\", \"/trigger\")\ntopic   = rospy.get_param(\"~topic\",   \"/triggerings\")\nrospy.set_param(\"~service\", service)\nrospy.set_param(\"~topic\",   topic)\nsrv = rospy.Service(service, std_srvs.srv.Trigger, on_trigger)\npub = rospy.Publisher(topic, std_msgs.msg.Bool, latch=True, queue_size=2)\nrospy.spin()\n```\n\nEquivalent ROS2 code using rclpy:\n\n```python\nimport rclpy, rclpy.qos, std_msgs.msg, std_srvs.srv\n\ndef on_trigger(req, resp):\n    pub.publish(std_msgs.msg.Bool(data=True))\n    resp.success = True\n    resp.message = \"Triggered!\"\n    return resp\n\nrclpy.init()\nnode = rclpy.create_node(\"mynode\")\nservice = node.get_parameter_or(\"service\", rclpy.Parameter(\"\", value=\"/trigger\")).value\ntopic   = node.get_parameter_or(\"topic\",   rclpy.Parameter(\"\", value=\"/triggerings\")).value\nif not node.has_parameter(\"service\"):\n    node.declare_parameter(\"service\", service)\nif not node.has_parameter(\"topic\"):\n    node.declare_parameter(\"topic\", topic)\nsrv = node.create_service(std_srvs.srv.Trigger, service, on_trigger)\nqos = rclpy.qos.QoSProfile(depth=2, durability=rclpy.qos.DurabilityPolicy.TRANSIENT_LOCAL)\npub = node.create_publisher(std_msgs.msg.Bool, topic, qos)\nrclpy.spin(node)\n```\n\nAny ROS2-only extras used will be ignored under ROS1, like topic QoS reliability.\n\n\n### Parameters\n\nrosros provides a convenient way to auto-load all node parameters,\nand override or populate additional parameters from a nested dictionary.\n\n```python\nrosros.init_node(\"mynode\")\n# If the given defaults were not among existing node parameters,\n# they will be auto-declared on the node.\nparams = rosros.init_params({\"my\": {\"nested\": {\"param1\": 5, \"param2\": True}}})\n```\n\n\n### Constructor args in publishers and services\n\nrosros provides short-hand convenience wrappers for publishing messages,\ninvoking services and returning service responses, without needing to\ncreate the message or service request/response class instance explicitly.\n\nROS1 already provides this short-hand in `rospy`,\nbut ROS2 API requires the instances to be created explicitly.\n\nrosros also supports giving message or service request as dictionary.\n\nIn rosros:\n\n```python\npub = rosros.create_publisher(\"/topic\", \"std_msgs/Bool\")\npub.publish(True)\npub.publish(data=True)\npub.publish({\"data\": True})\npub.publish()\n\n# Service callback need not accept any parameters if request object is irrelevant\nsrv = rosros.create_service(\"/service\", \"std_srvs/SetBool\", lambda: {\"message\": \"Triggered!\"})\n\ncli = rosros.create_client(\"/service\", \"std_srvs/SetBool\")\ncli.call(True)\ncli.call(data=True)\ncli.call({\"data\": True})\ncli(True)\n```\n\nEquivalent ROS1 code using rospy:\n\n```python\npub = rospy.Publisher(\"/topic\", std_msgs.msg.Bool, queue_size=0)\npub.publish(True)\npub.publish(data=True)\npub.publish(std_msgs.msg.Bool(True))\npub.publish()\n\nsrv = rospy.Service(\"/service\", std_srvs.srv.SetBool, lambda req: {\"message\": \"Triggered!\"})\n\ncli = rospy.ServiceProxy(\"/service\", std_srvs.srv.SetBool)\ncli.call(True)\ncli.call(data=True)\ncli.call(std_srvs.srv.SetBoolRequest(True))\ncli(True)\n```\n\nEquivalent ROS2 code using rospy:\n\n```python\npub = mynode.create_publisher(std_msgs.msg.Bool, \"/topic\", 0)\npub.publish(std_msgs.msg.Bool(data=True))\npub.publish(std_msgs.msg.Bool(data=True))\npub.publish(std_msgs.msg.Bool(data=True))\npub.publish(std_msgs.msg.Bool())\n\ndef callback(req, resp):\n  resp.message = \"Triggered!\"\n  return resp\nsrv = mynode.create_service(std_srvs.srv.SetBool, \"/service\", callback)\n\ncli = mynode.create_client(std_srvs.srv.SetBool, \"/service\")\ncli.call(std_srvs.srv.SetBool.Request(data=True))\n```\n\n### Timers\n\nrosros provides convenience one-shot timers in ROS2 like ROS1 does.\n\nAdditionally, rosros provides an option to start invoking the timer callback\nimmediately, instead of waiting one period before first call.\n\n```python\ntimer = rosros.create_timer(2, lambda: print(\"just once, after 2 sec\"), oneshot=True)\n\ntimer = rosros.create_timer(5, lambda: print(\"every 5 sec, starting now\"), immediate=True)\n\n# Can take seconds, can take an explicit Duration object\ntimer = rosros.create_timer(rosros.api.make_duration(5), lambda: print(\"every 5 sec\"))\n```\n\n### Bags\n\nrosros provides a unified interface for reading and writing ROS bags.\n\nBag format is the custom ROS format in ROS1, and the SQLite database format in ROS2.\n\n```python\nbag = rosros.Bag(\"my.bag\", mode=\"a\")\nprint(bag)  # Prints bag metainfo, equivalent to \"rosbag info\"\n\nbag.write(\"/my/topic\", std_msgs.msg.Bool())\n\nfor topic, message, stamp in bag:\n    print(\"[%s] %s:  %s\" % (rosros.api.to_sec(stamp), topic, message))\n```\n\n\n\n\nROS core functionality\n----------------------\n\nFunctionality for creating and operating a ROS node.\n\nObjects returned are standard `rospy` / `rclpy` API objects, patched with\nadditional members for a unified interface _**conforming to ROS1 API**_.\n\n\n| Name                                | Description                                                                                | Arguments\n| ----------------------------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------\n|                                     | **Startup, spin and shutdown**                                                             | |\n| `rosros.init_node`                  | initializes ROS and creates ROS node (and returns Node object in ROS2)                     | `name, args=None, namespace=None, anonymous=True, log_level=None, enable_rosout=True, multithreaded=True, reentrant=False`\n| `rosros.start_spin`                 | sets ROS node spinning forever in a background thread                                      | |\n| `rosros.spin`                       | spins ROS node forever                                                                     | |\n| `rosros.spin_once`                  | waits until timeout in ROS1; executes one ROS operation or waits until timeout in ROS2     | `timeout=None`\n| `rosros.spin_until_future_complete` | spins ROS until future complete or timeout reached or ROS shut down                        | `future, timeout=None`\n| `rosros.ok`                         | returns whether ROS has been initialized and is not shut down                              | |\n| `rosros.on_shutdown`                | registers function to be called on shutdown                                                | `callback, *args, **kwargs`\n| `rosros.shutdown`                   | shuts down live ROS node, if any                                                           | `reason=None`\n|                                     |                                                                                            | |\n|                                     | **Parameters**                                                                             | |\n| `rosros.init_params`                | sets all parameters on node from defaults dictionary, returns full parameters dictionary.  | `defaults=None, **defaultkws`\n|                                     | In ROS2, auto-declares unregistered parameters.                                            | |\n| `rosros.has_param`                  | returns whether the parameter exists                                                       | `name`\n| `rosros.get_param`                  | returns parameter value from current ROS node                                              | `name, default=None, autoset=True`\n| `rosros.get_param_names`            | returns the names of all current ROS node parameters                                       | |\n| `rosros.get_params`                 | returns the current ROS node parameters, by default as nested dictionary                   | `nested=True`\n| `rosros.set_param`                  | sets a parameter on the node.                                                              | `name, value, descriptor=None`\n|                                     | In ROS2, parameter will be auto-declared if unknown so far.                                | |\n| `rosros.delete_param`               | deletes parameter from the node                                                            | `name`\n|                                     |                                                                                            | |\n|                                     | **Topics, services, timers and rates**                                                     | |\n| `rosros.create_publisher`           | returns a ROS publisher instance                                                           | `topic, cls_or_typename, latch=False, queue_size=0, **qosargs`\n| `rosros.create_subscriber`          | returns a ROS subscriber instance                                                          | `topic, cls_or_typename, callback, callback_args=None, queue_size=0, raw=False, **qosargs`\n| `rosros.create_service`             | returns a ROS service server instance, for providing a service                             | `service, cls_or_typename, callback, **qosargs`\n| `rosros.create_client`              | returns a ROS service client instance, for invoking a service                              | `service, cls_or_typename, **qosargs`\n| `rosros.create_timer`               | returns a ROS timer instance                                                               | `period, callback, oneshot=False, immediate=False`\n| `rosros.create_rate`                | returns a ROS rate instance, for sleeping at a fixed rate                                  | `frequency`\n| `rosros.destroy_entity`             | closes the given publisher, subscriber, service client, service server, or timer instance  | `item`\n| `rosros.AnyMsg`                     | `rospy.AnyMsg` in ROS1, stand-in class with equivalent functionality in ROS2               | |\n| `rosros.sleep`                      | sleeps for the specified duration in ROS time                                              | `duration`\n| `rosros.wait_for_publisher`         | blocks until topic has at least one publisher                                              | `topic, timeout=None, cls_or_typename=None`\n| `rosros.wait_for_subscriber`        | blocks until topic has at least one subscriber                                             | `topic, timeout=None, cls_or_typename=None`\n| `rosros.wait_for_service`           | blocks until service is available                                                          | `service, timeout=None, cls_or_typename=None`\n|                                     |                                                                                            | |\n|                                     | **ROS bags**                                                                               | |\n| `rosros.Bag`                        | ROS bag reader and writer;                                                                 | |\n|                                     | `rosbag.Bag` in ROS1 with some additional functionality;                                   | |\n|                                     | equivalent class in ROS2, using the ROS2 default SQLite format                             | |\n|                                     |                                                                                            | |\n|                                     | **Information queries**                                                                    | |\n| `rosros.get_namespace`              | returns ROS node namespace                                                                 | |\n| `rosros.get_node_name`              | returns ROS node full name with namespace                                                  | |\n| `rosros.get_nodes`                  | returns all ROS nodes, as `[node full name, ]`                                             | |\n| `rosros.get_topics`                 | returns all available ROS topics, as `[(topic name, [type name, ]), ]`                     | |\n| `rosros.get_services`               | returns all available ROS services, as `[(service name, [type name, ]), ]`                 | |\n| `rosros.remap_name`                 | returns the absolute remapped topic/service name if mapping exists                         | `name, namespace=None`\n| `rosros.resolve_name`               | returns absolute remapped name, namespaced under current node if relative or private       | `name, namespace=None`\n|                                     |                                                                                            | |\n|                                     | **Miscellaneous**                                                                          | |\n| `rosros.get_logger`                 | returns `logging.Logger` for logging to ROS log handler;                                   | |\n|                                     | logger supports additional keywords `__once__`, `__throttle__`, `__throttle_identical__`   | |\n| `rosros.get_ros_version`            | returns ROS version information as a data dictionary                                       | |\n| `rosros.get_rostime`                | returns ROS time instance for current ROS clock time                                       | |\n| `rosros.register_init`              | informs `rosros` of ROS having been initialized outside of `init_node()`.                  | `node=None`\n|                                     | Giving node as argument is mandatory in ROS2.                                              | |\n\n\n### Patches in ROS1\n\nAdditional functionality patched onto `rospy` classes for convenience:\n\n- `rospy.ServiceProxy.call_async(*args, **kwargs):`\n  makes service call in a background thread, returns `concurrent.futures.Future`-like response\n- `rospy.ServiceProxy.wait_for_service(timeout=None, timeout_sec=None):`\n  waits for service to become available, returns `True`, or `False` on timeout\n- `rospy.ServiceProxy.service_is_ready():`\n   returns whether service is currently available\n- `rospy.Subscriber:`\n  callbacks are invoked with serialized message bytes if subscription was created with `raw=True`\n- `rosbag.Bag.get_message_definition(msg_or_type):`\n   returns ROS1 message type definition full text from bag\n- `rosbag.Bag.get_message_class(typename, typehash=None):`\n   returns ROS1 message class\n- `rosbag.Bag.get_message_type_hash(msg_or_type):`\n   returns ROS1 message type MD5 hash\n- `rosbag.Bag.get_topic_info():`\n  returns topic and message type metainfo as `{(topic, typename, typehash): count}`\n- `rosbag.Bag.reindex_file(f):`\n  reindexes bag file on disk; makes a temporary copy in file directory\n\n\n### Patches in ROS2\n\nThe following classes are patched with full conformity to their equivalents in ROS:\n\n- `rclpy.client.Client`\n- `rclpy.publisher.Publisher`\n- `rclpy.service.Service`\n- `rclpy.subscription.Subscription`\n- `rclpy.duration.Duration`\n- `rclpy.time.Time`\n- `rclpy.timer.Rate`\n- `rclpy.timer.Timer`\n\nE.g. durations support `+-*/` arithmetic, and publisher / subscriber provide `.md5sum` for message type hash.\n\n\nAPI helpers\n-----------\n\nFunctionality for working with message types and data and metainfo.\nCan be used as stand-alone library functions without initializing rosros core.\n\n| Name                                    | Description                                                                                   | Arguments\n| --------------------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------\n|                                         | **Message types and instances**                                                               | |\n| `rosros.api.get_message_class`          | returns ROS message / service class object, like `std_msgs.msg.Bool` for `\"std_msgs/Bool\"`    | `msg_or_type`\n| `rosros.api.get_message_definition`     | returns ROS message or service request/response type definition text,                         | `msg_or_type, full=True`\n|                                         | by default including message subtype definitions                                              | |\n| `rosros.api.get_message_fields`         | returns `{field name: field type name}` if ROS message or service request/response, else `{}` | `val`\n| `rosros.api.get_message_header`         | returns message `Header`-attribute if any, else `None`                                        | `val`\n| `rosros.api.get_message_type`           | returns ROS message / service canonical type name, like `\"std_msgs/Header\"`                   | `msg_or_cls`\n|                                         | or `\"*\"` for `AnyMsg`                                                                         | |\n| `rosros.api.get_message_type_hash`      | returns ROS message / service type MD5 hash                                                   | `msg_or_type`\n| `rosros.api.get_message_value`          | returns message attribute value, with numeric arrays converted to lists                       | `msg, name, default=...`\n| `rosros.api.is_ros_message`             | returns whether value is a ROS message or service request/response class or instance          | `val`\n|                                         |                                                                                               | |\n|                                         | **Message conversion**                                                                        | |\n| `rosros.api.deserialize_message`        | returns ROS message or service request/response instantiated from serialized binary           | `raw, cls_or_typename`\n| `rosros.api.dict_to_message`            | returns ROS message populated from Python dictionary                                          | `dct, msg_or_type`\n| `rosros.api.message_to_dict`            | returns ROS message as nested Python dictionary;                                              | `msg, replace=None`\n|                                         | with optional replacements for certain values like `{math.nan: None}`                         | |\n| `rosros.api.message_to_str`             | returns ROS message as an evaluatable string, e.g. `\"std_msgs.msg.UInt8(data=0)\"`             | `msg, indent=None`\n| `rosros.api.serialize_message`          | returns ROS message or service request/response as a serialized binary of `bytes()`           | `msg`\n|                                         |                                                                                               | |\n|                                         | **Service classes and definitions**                                                           | |\n| `rosros.api.get_service_definition`     | returns ROS service type definition text                                                      | `srv_or_type`\n| `rosros.api.get_service_request_class`  | returns ROS service request class object                                                      | `srv_or_type`\n| `rosros.api.get_service_response_class` | returns ROS service response class object                                                     | `srv_or_type`\n| `rosros.api.is_ros_service`             | returns whether value is a ROS service class object                                           | `val`\n|                                         |                                                                                               | |\n|                                         | **Time and duration**                                                                         | |\n| `rosros.api.is_ros_time`                | returns whether value is a ROS time/duration                                                  | `val`\n| `rosros.api.make_duration`              | returns a ROS duration                                                                        | `secs=0, nsecs=0`\n| `rosros.api.make_time`                  | returns a ROS time                                                                            | `secs=0, nsecs=0`\n| `rosros.api.time_category`              | returns \"time\" or \"duration\" for time/duration type, else value                               | `msg_or_type`\n| `rosros.api.time_message`               | returns ROS2 time/duration from `rclpy` as `builtin_interfaces` or vice versa,                | `val, to_message=True, clock_type=None`\n|                                         | or value if not convertible                                                                   | |\n| `rosros.api.to_datetime`                | returns value as `datetime.datetime` if value is ROS time/duration, else value                | `val`\n| `rosros.api.to_decimal`                 | returns value as `decimal.Decimal` if value is ROS time/duration, else value                  | `val`\n| `rosros.api.to_duration`                | returns value as ROS duration if convertible (int/float/time/datetime/decimal), else value    | `val`\n| `rosros.api.to_nsec`                    | returns value in nanoseconds if value is ROS time/duration, else value                        | `val`\n| `rosros.api.to_sec`                     | returns value in seconds if value is ROS time/duration, else value                            | `val`\n| `rosros.api.to_sec_nsec`                | returns value in (seconds, nanoseconds) if value is ROS time/duration, else value             | `val`\n| `rosros.api.to_time`                    | returns value as ROS time if convertible (int/float/duration/datetime/decimal), else value    | `val`\n|                                         |                                                                                               | |\n|                                         | **Class and type names**                                                                      | |\n| `rosros.api.canonical`                  | returns `\"pkg/Type\"` for `\"pkg/subdir/Type\"`                                                  | `typename`\n| `rosros.api.get_alias_type`             | returns ROS built-in type for alias like `\"char\"`, if any; reverse of `get_type_alias()`.     | `typename`\n|                                         | In ROS1, `byte` and `char` are aliases for `int8` and `uint8`; in ROS2 the reverse.           | |\n| `rosros.api.get_type_alias`             | returns alias like `\"char\"` for ROS built-in type, if any; reverse of `get_alias_type()`.     | `typename`\n|                                         | In ROS1, `byte` and `char` are aliases for `int8` and `uint8`; in ROS2 the reverse.           | |\n| `rosros.api.make_full_typename`         | returns `\"pkg/msg/Type\"` or `\"pkg/srv/Type\"` for `\"pkg/Type\"`                                 | `typename, category=\"msg\"`\n| `rosros.api.scalar`                     | returns scalar type from ROS message data type, like `\"uint8\"` from `uint8`-array;            | `typename`\n|                                         | in ROS2, returns unbounded type, e.g. `\"string\"` from `\"string<=10[<=5]\"`.                    | |\n|                                         |                                                                                               | |\n|                                         | **Miscellaneous**                                                                             | |\n| `rosros.api.format_param_name`          | returns parameter name with correct separator for ROS version, and leading sigils stripped    | `name`\n| `rosros.api.PARAM_SEPARATOR`            | separator char between ROS parameter namespace parts                                          | |\n| `rosros.api.ROS_BUILTIN_TYPES`          | list of ROS built-in numeric and string type names, like `[\"string\", \"uint8\", ..]`            | |\n| `rosros.api.ROS_NUMERIC_TYPES`          | list of ROS built-in numeric type names, like `[\"bool\", \"int8\", ..]`                          | |\n| `rosros.api.ROS_STRING_TYPES`           | list of ROS built-in string type names                                                        | |\n| `rosros.api.ROS_TIME_CLASSES`           | ROS time/duration types mapped to type names                                                  | |\n| `rosros.api.ROS_TIME_TYPES`             | list of ROS built-in time/duration type names                                                 | |\n\n\nConverting an existing package\n------------------------------\n\n### Existing ROS1 package to ROS1/ROS2 package\n\nrosros can be used as a (mostly) drop-in replacement for rospy\nto make a ROS1 package equally usable under ROS2 - if the code relies only\non `rospy`, and does not go too deeply into using ROS1 specifics like `rosgraph`.\n\n`rospy` imports can be replaced with `rosros.rospify`, e.g.\n\n```python\nimport threading\nfrom rosros import rospify as rospy\nfrom std_msgs.msg import String\n\nrospy.init_node(\"talker\", anonymous=True)\npub = rospy.Publisher(\"chatter\", String, queue_size=10)\nrate = rospy.Rate(10)  # 10hz\nthreading.Thread(target=rospy.spin).start()  # Concession for ROS2: spin is always needed\nwhile not rospy.is_shutdown():\n    hello_str = \"hello world %s\" % rospy.get_time()\n    rospy.loginfo(hello_str)\n    pub.publish(hello_str)\n    rate.sleep()\n```\n\nIn ROS1 it provides `rospy`, and in ROS2 it provides matching API classes and functions.\n\nMain behavioral differences of `rospify` in ROS2 vs ROS1:\n- `Subscriber.get_num_connections()` always returns 0, as ROS2 does not provide this information\n- subscribing with `AnyMsg` needs the publisher existing\n- parameter operations always apply to node parameters, as ROS2 has no global parameters\n- functions registered in `on_shutdown()` are called after node shutdown, not before\n- `rosros.rospify.get_master()` requires local node to be initialized, unlike `rospy.get_master()`\n- `rosros.rospify.SubscribeListener` does nothing\n\n\n### Existing ROS2 package to ROS1/ROS2 package\n\nrosros can also be used as a (mostly) drop-in replacement for rclpy\nto make a ROS2 package equally usable under ROS1 - if the code mainly relies on\n`rclpy` module and does not go deep into using ROS2 specifics like `builtin_interfaces`.\n\n`rclpy` imports can be replaced with `rosros.rclify`, e.g.\n\n```python\nfrom rosros import rclify as rclpy\nfrom std_msgs.msg import String\n\nclass MinimalPublisher(rclpy.node.Node):\n    def __init__(self):\n        super().__init__(\"minimal_publisher\")\n        self.publisher_ = self.create_publisher(String, \"chatter\", 10)\n        timer_period = 0.1  # seconds\n        self.timer = self.create_timer(timer_period, self.timer_callback)\n        self.i = 0\n    def timer_callback(self):\n        msg = String()\n        msg.data = \"Hello World: %d\" % self.i\n        self.publisher_.publish(msg)\n        self.get_logger().info('Publishing: \"%s\"' % msg.data)\n        self.i += 1\n\nrclpy.init()\nminimal_publisher = MinimalPublisher()\nrclpy.spin(minimal_publisher)\n```\n\nIn ROS2, `rclify` provides `rclpy`, and in ROS1 it provides\nmatching API modules and classes and functions.\n\nCovers all the parts of `rclpy.node` that are supportable in ROS1, focused on:\ncreating nodes, publishing and subscribing topics, providing and invoking services,\nworking with rates and timers.\n\nMain behavioral differences of `rclify` in ROS1 vs ROS2:\n- one single node as always in ROS1\n- QoS profiles ignore all options except `depth` (taken as `queue_size`),\n  and `durability` equaling `TRANSIENT_LOCAL` (taken as `latch=True`)\n- no contexts, no executors, no waitables\n- no callback groups, no guard conditions, no middleware events\n\nMethods pertaining to unsupported functionality do nothing and return `None`.\nUnsupported arguments in methods are ignored.\n\n\nInstallation\n------------\n\nDeveloped and tested under ROS1 Noetic and ROS2 Foxy,\nworks in later ROS2 versions (tested up to ROS2 Jazzy). \n\n### Using pip\n\n    pip install rosros\n\nThis will make the `rosros` library available to Python packages.\n\nRequires ROS Python packages\n(ROS1: rospy, roslib, rosbag, rosservice, genpy;\n ROS2: rclpy, rosidl_parser, rosidl_runtime_py, ament_index_python, builtin_interfaces).\n\n\n### Using catkin\n\nIn a ROS1 workspace, under the source directory:\n\n    git clone https://github.com/suurjaak/rosros.git\n    cd rosros\n    catkin build --this\n\nThis will make the `rosros` library available in the ROS1 workspace.\n\n\n### Using colcon\n\nIn a ROS2 workspace, at the workspace root:\n\n    git clone https://github.com/suurjaak/rosros.git src/rosros\n    colcon build --packages-select rosros\n\nThis will make the `rosros` library available in the ROS2 workspace.\n\n\nRunning tests\n-------------\n\nROS1:\n\n    catkin test rosros\n\nROS2:\n\n    colcon test --packages-select rosros\n\nTo run ROS2 tests with full log output:\n\n    colcon test --packages-select rosros --event-handlers console_direct+ \\\n                --pytest-args \"--capture=tee-sys\"\n\nUsing pytest, either ROS1 or ROS2 (roscore needs to be running if ROS1):\n\n    cd rosros\n    pytest\n\n\nDependencies\n------------\n\n- pyyaml (https://pypi.org/project/PyYAML)\n\nROS1:\n- genpy\n- rosbag\n- roslib\n- rospy\n- rosservice\n\nROS2:\n- ament_index_python\n- builtin_interfaces\n- rclpy\n- rosidl_parser\n- rosidl_runtime_py\n\nROS2 test dependencies:\n- pytest (https://pypi.org/project/pytest)\n- pytest-forked (https://pypi.org/project/pytest-forked)\n\nFor generating API documentation:\n\n- doxypypy (https://pypi.org/project/doxypypy;\n            needs latest master: `pip install git+https://github.com/Feneric/doxypypy`)\n\n\nAttribution\n-----------\n\nThis package includes partially copied and modified code from `rclpy` core library,\nreleased under the Apache-2.0 license.\nSee [doc/3rd-party licenses.txt](doc/3rd-party%20licenses.txt) for full details.\n\n\nLicense\n-------\n\nCopyright (c) 2022 by Erki Suurjaak.\nReleased as free open source software under the BSD License,\nsee LICENSE.md for full details.\n\n\n",
    "bugtrack_url": null,
    "license": "BSD",
    "summary": "Simple unified interface to ROS1 / ROS2 Python API",
    "version": "0.2.6",
    "project_urls": {
        "Homepage": "https://github.com/suurjaak/rosros"
    },
    "split_keywords": [
        "ros",
        "ros1",
        "ros2"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8df03b866158a8bbe2e90f5d6101a1ac23e899e5c86d7735d8552c3cbb2dbe78",
                "md5": "44249d8226352470397172767a539fd7",
                "sha256": "3f41792da618ff75d8c93a7d248d2eb0ddcdca7c401a938cbfdb44493bc08759"
            },
            "downloads": -1,
            "filename": "rosros-0.2.6-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "44249d8226352470397172767a539fd7",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3",
            "size": 116472,
            "upload_time": "2024-11-20T19:16:07",
            "upload_time_iso_8601": "2024-11-20T19:16:07.445441Z",
            "url": "https://files.pythonhosted.org/packages/8d/f0/3b866158a8bbe2e90f5d6101a1ac23e899e5c86d7735d8552c3cbb2dbe78/rosros-0.2.6-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-11-20 19:16:07",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "suurjaak",
    "github_project": "rosros",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [
        {
            "name": "pyyaml",
            "specs": []
        }
    ],
    "lcname": "rosros"
}
        
Elapsed time: 0.38971s