Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 144 additions & 9 deletions src/pycyphal2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

Supports various transports such as Ethernet (UDP) and CAN FD with optional redundancy.

## Installation
# Installation

Optional features inside the brackets can be removed if not needed; see `pyproject.toml` for the full list:

```
pip install 'pycyphal2[udp,pythoncan]'
```

## Usage
# Usage

Set up a transport, make a node, publish and subscribe:

Expand All @@ -40,9 +40,144 @@ async def main():
Transport modules (`pycyphal2.udp`, `pycyphal2.can`) are imported separately
so that only the needed dependencies are pulled in.

### Name resolution
## Name resolution

The topic naming system shares many similarities with [ROS Names](https://wiki.ros.org/Names).

Name resolution is the process by which a topic name passed to `node.advertise()` or `node.subscribe()` is resolved to a topic name as used on the Cyphal network.
There exist 4 kinds of topic names in Cyphal:

<details markdown="1">
<summary>Relative Name</summary>

A relative name is a name that does not start with `/` or `~/`.

```
sensor/temperature
cmd_vel
camera/image_raw
```

Its resolved name is prefixed with the node namespace.

| Input name | Namespace | Home | Resolved name |
| ----------------- | --------- | ---- | --------------------- |
| `foo` | `ns` | `me` | `ns/foo` |
| `foo/bar` | `ns` | `me` | `ns/foo/bar` |

*Use case:* Use relative names for topics that are specific to a node, but might be reused across multiple nodes.

*Example:* A robot contains 4 motor controllers of the same type, each has its own namespace (`motor_1`, `motor_2`, `motor_3`, `motor_4`).
Using relative names, the application code is the same for all 4 motor controllers, however the topics are resolved differently based on the node namespace.

| Input name | Namespace | Home | Resolved name |
| ----------------- | --------- | ------- | ---------------------------- |
| `speed` | `motor_1` | `robot` | `motor_1/speed` |
| `speed` | `motor_2` | `robot` | `motor_2/speed` |
| `speed` | `motor_3` | `robot` | `motor_3/speed` |
| `speed` | `motor_4` | `robot` | `motor_4/speed` |

</details>

<details markdown="1">
<summary>Absolute Name</summary>

An absolute name starts with `/`.

```
/temperature
/diagnostics/status
```

Its resolved name is simply the same as the input name, ignoring both the node namespace and home.

| Input name | Namespace | Home | Resolved name |
| ----------------- | --------- | ---- | ------------------ |
| `foo` | `ns` | `me` | `foo` |
| `foo/bar` | `ns` | `me` | `foo/bar` |

Use case: Use absolute names for topics that are *not* specific to a node and might be reused across multiple nodes.

Example: Shared system topics like `/log`, since multiple nodes may publish to the same topic.
Conversely, topics like `/battery_voltage` that might be sourced from multiple nodes but need one single source of truth for other nodes like the motor controllers to subscribe to.

| Input name | Namespace | Home | Resolved name |
| --------------------- | --------------- | ------- | ------------------ |
| `/log` | `cpu` | `robot` | `/log` |
| `/battery_voltage` | `battery_1` | `robot` | `/battery_voltage` |

</details>

<details markdown="1">
<summary>Homeful Name</summary>

A homeful name starts with `~` of `~/`.

```
~/config
```

Its resolved name consists of the home and the input name, with the node namespace ignored. Note that `~foo` is not homeful and resolves as relative name to `ns/~foo` (this is confusing so don't use this).

Proposal: both `~` and `~foo` should not be allowed.

| Input name | Namespace | Home | Resolved name |
| --------------------- | --------------- | ------- | ------------------ |
| `~` | `ns` | `me` | `me` |
| `~/config` | `ns` | `me` | `me/config` |
| `~config` | `ns` | `me` | `ns/~config` |

Use case: For topics tied to specific nodes. The most common use case is configuring a node's parameters or settings.

Example: We want to configure an antenna to transmit at a specific frequency. The antenna node has a `~/config/frequency` topic that we can publish to.

</details>

<details markdown="1">
<summary>Pattern Name (only for subscribing)</summary>

A pattern name contains wildcard `*` (matches any _single_ name segment)

```
*/speed # matches any topic under `speed` (e.g. `motor_1/speed`, `motor_2/speed/`, `motor_3/value`, ...)
```

or `>` (matches _zero or more_ trailing segments)

```
sensor/> # matches any topic under `sensor` (e.g. `sensor/`, `sensor/temperature`, `sensor/pressure/`, ...)
```

Use case: Use `*` to subscribe to a _specific_ topic coming from a undetermined number of nodes.
Use `>` to subscribe to _multiple_ topics under a given namespace.

Example: `*/battery_pct` to subscribe to all nodes publishing battery data, of which there may be multiple per vehicle.
'logs/>' to subscribe to all topics publishing under '/logs' which may contain 'log_info', 'log_warning', 'log_error' topics.

</details>

Used effectively they allow to split up complex systems into smaller sub-systems simplifying development and debugging.

### Extra functions

*Topping* is the process by which a unique subject ID is assigned upon initialization.
For some applications that require a high level of reliability, determinism is required and can be achieved by using `#` to pin a topic to a specific subject ID.

```
motor/speed#1234
```

The resolved topic name is 'motor/speed' and the subject ID is fixed to 1234.

*Remapping* lets a node replace one name with another before final resolution.
This can be useful when trying to match the expected topic name from one node to another (when integrating multiple subsystems).

```
node.remap({"sensor/temperature": "temp"})
```

Now any topic name matching `sensor/temperature` will be remapped to `temp` before final resolution.

The topic naming system shares many similarities with ROS.
A valid name contains printable ASCII characters except space (ASCII codes [33, 126]).
Normalized names do not have leading or trailing segment separators `/` and do not have consecutive separators.
Every node should have a unique name, which is called its *home*; home substitution is done via `~/`.
Expand All @@ -66,7 +201,7 @@ async def main():

See also :meth:`Node.remap`.

### Publish
## Publish

Publication is best-effort by default. Pass `reliable=True` when publishing to retry delivery until
acknowledged by every known subscriber or until the deadline; if the remote side does not acknowledge in time,
Expand All @@ -77,7 +212,7 @@ async def main():
await pub(Instant.now() + 1.0, b"payload", reliable=True)
```

### Subscribe
## Subscribe

Subscriptions normally yield messages as soon as they arrive. Set `reordering_window` [seconds] on
:meth:`Node.subscribe` to allow delaying out-of-order messages to reconstruct the original publication order.
Expand All @@ -99,7 +234,7 @@ async def main():
print(topic.name, captures) # [('engine', 1)], where 1 is the pattern segment index
```

### RPC & streaming
## RPC & streaming

RPC is layered directly on top of pub/sub. Use :meth:`Publisher.request` to publish a message that expects
responses, and use :attr:`Arrival.breadcrumb` on the subscriber side to send a unicast reply back to the requester.
Expand All @@ -119,7 +254,7 @@ async def main():
await arrival.breadcrumb(Instant.now() + 1.0, b"chunk-2", reliable=True)
```

### Topic pinning
## Topic pinning

Topics may be pinned to a specific subject-ID using `name#1234` to bypass automatic assignment.
This is useful for applications where a high degree of determinism is required and for Cyphal/CAN v1.0 interoperability.
Expand All @@ -135,7 +270,7 @@ async def main():
Old Cyphal/CAN v1.0 nodes do not participate in the topic discovery protocol,
so topics joined only by such nodes are not discoverable by pattern subscribers.

## Remarks
# Remarks

Cyphal does not define a serialization format. Previous versions used to define the DSDL format but it has been
extracted into an independent project, and Cyphal was made serialization-agnostic in v1.1+.
Expand Down
Loading