Metadata-Version: 2.4
Name: rosless
Version: 0.3.0
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: System :: Networking
Summary: Talk to ROS 2 topics without installing ROS
Keywords: ros,ros2,dds,rtps,robotics
Author: Jeff Hykin
License-Expression: Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/jeff-hykin/rosless
Project-URL: Repository, https://github.com/jeff-hykin/rosless

# rosless

Talk to ROS 2 topics without installing ROS.

ROS 2 has no wire protocol of its own. It is plain DDS/RTPS plus two naming
conventions, so a program that speaks RTPS and knows those conventions is
indistinguishable from a ROS node on the wire. This package does exactly that,
from Rust, with no rclpy, no sourced workspace, and no ROS installation.

```sh
pip install rosless
```

## Usage

```python
import rosless

for topic in rosless.topics(wait=3.0):
    print(topic.name, topic.type_name, topic.publishers, topic.subscribers)

with rosless.subscribe("/imu") as imu:
    for message in imu:
        print(message["angular_velocity"]["z"])
```

Messages whose type is in the built-in catalogue (248 definitions across 24
packages, including `sensor_msgs/msg/PointCloud2`, `nav_msgs/msg/Odometry`, and
`tf2_msgs/msg/TFMessage`) arrive as plain dicts. `uint8[]` sequences arrive as
`bytes`, not as a million-element list, so `numpy.frombuffer(message["data"],
numpy.uint8)` gives you the pixels without another copy. Fixed-size arrays such
as `uint8[4]` stay numeric.

Anything not catalogued arrives as `bytes` holding the raw CDR body:

```python
subscription = rosless.subscribe("/widgets")
if not subscription.is_catalogued:
    body = subscription.read()          # bytes, encapsulation header stripped
```

## Publishing

```python
with rosless.advertise("/camera", "sensor_msgs/msg/Image") as camera:
    camera.send({"height": 480, "width": 640, "encoding": "rgb8", "data": frame})
```

Fields you leave out take the ROS default. `data` accepts `bytes`, a
`bytearray`, a `memoryview`, or a numpy array, and a message can be any object
whose attributes are named after the fields, so a message class from another
library publishes without being converted first.

DDS drops anything written before a subscriber has matched, so check
`publisher.subscriber_count` before sending the message that matters.

## API

| Call | What it does |
| --- | --- |
| `discover(domain=0, wait=2.0)` | Topics, services, and participant count |
| `topics(...)` / `services(...)` | Just one half of the graph |
| `subscribe(topic, domain=0, wait=2.0, ...)` | A `Subscription`, iterable and a context manager |
| `advertise(topic, type_name, domain=0, ...)` | A `Publisher`, also a context manager |
| `decode(type_name, payload)` | Decode CDR including the 4-byte header |
| `decode_body(type_name, body, little_endian=True)` | Decode a header-stripped body |
| `encode(type_name, message)` | The inverse of `decode` |
| `type_names()` / `lookup(type_name)` | Browse the catalogue |
| `mangle_topic` / `demangle_topic` | `/chatter` ⇄ `rt/chatter` |
| `mangle_type` / `demangle_type` | `std_msgs/msg/String` ⇄ `std_msgs::msg::dds_::String_` |

`subscribe` and `advertise` both take `reliable`, `transient_local`, and `depth`
to describe QoS. A reader defaults to best effort because that matches every
writer; a writer defaults to reliable. Two ends that disagree never match, and
DDS reports no error when they don't.

`Subscription.read(timeout=None)` returns `None` when the timeout expires. Every
blocking call releases the GIL and stays interruptible with Ctrl-C.

Source and the matching command line tool: <https://github.com/jeff-hykin/rosless>

