From 02c1c52fa211543257e09a42837c37881917d842 Mon Sep 17 00:00:00 2001 From: Maksim Drachov Date: Mon, 25 May 2026 16:11:40 +0300 Subject: [PATCH 1/2] New docs --- src/pycyphal2/__init__.py | 122 +++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/src/pycyphal2/__init__.py b/src/pycyphal2/__init__.py index 88043d9b..b5d1da0b 100644 --- a/src/pycyphal2/__init__.py +++ b/src/pycyphal2/__init__.py @@ -42,7 +42,127 @@ async def main(): ### Name resolution -The topic naming system shares many similarities with ROS. +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, used effectively they allow the developer to split up complex systems into smaller sub-systems, simplifying development and debugging. + +#### 1. Relative Name + +A relative name is a name that does not start with '/' or `~/`. + +``` +sensor/temperature +cmd_vel +camera/image_raw +``` + +It's 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` | + +#### 2. Absolute Name + +An absolute name starts with `/`. + +``` +/temperature +/diagnostics/status +``` + +It ignores the node namespace. + +| 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 shared 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` | + +#### 3. Homeful Name + +A homeful name starts with `~` of `~/`. + +``` +~/config +``` + +Note that the node namespace is ignored. Also note that `~foo` is not homeful and resolves as relative name to `ns/~foo` (this is confusing so don't use this). + +| 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. + +#### 4. Pattern Name + +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. + +#### 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. + 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 `~/`. From 222670b93de8d6f5cb5ce903d045c21e639c04b6 Mon Sep 17 00:00:00 2001 From: Maksim Drachov Date: Mon, 25 May 2026 16:24:30 +0300 Subject: [PATCH 2/2] formatting --- src/pycyphal2/__init__.py | 59 ++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/pycyphal2/__init__.py b/src/pycyphal2/__init__.py index b5d1da0b..43dc40b0 100644 --- a/src/pycyphal2/__init__.py +++ b/src/pycyphal2/__init__.py @@ -5,7 +5,7 @@ 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: @@ -13,7 +13,7 @@ pip install 'pycyphal2[udp,pythoncan]' ``` -## Usage +# Usage Set up a transport, make a node, publish and subscribe: @@ -40,17 +40,17 @@ 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: -There exist 4 kinds of topic names in Cyphal, used effectively they allow the developer to split up complex systems into smaller sub-systems, simplifying development and debugging. +
+Relative Name -#### 1. Relative Name - -A relative name is a name that does not start with '/' or `~/`. +A relative name is a name that does not start with `/` or `~/`. ``` sensor/temperature @@ -58,16 +58,16 @@ async def main(): camera/image_raw ``` -It's resolved name is prefixed with the node namespace. +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. +*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`). +*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 | @@ -77,7 +77,10 @@ async def main(): | `speed` | `motor_3` | `robot` | `motor_3/speed` | | `speed` | `motor_4` | `robot` | `motor_4/speed` | -#### 2. Absolute Name +
+ +
+Absolute Name An absolute name starts with `/`. @@ -86,14 +89,14 @@ async def main(): /diagnostics/status ``` -It ignores the node namespace. +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 shared across multiple nodes. +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. @@ -103,7 +106,10 @@ async def main(): | `/log` | `cpu` | `robot` | `/log` | | `/battery_voltage` | `battery_1` | `robot` | `/battery_voltage` | -#### 3. Homeful Name +
+ +
+Homeful Name A homeful name starts with `~` of `~/`. @@ -111,7 +117,9 @@ async def main(): ~/config ``` -Note that the node namespace is ignored. Also note that `~foo` is not homeful and resolves as relative name to `ns/~foo` (this is confusing so don't use this). +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 | | --------------------- | --------------- | ------- | ------------------ | @@ -123,7 +131,10 @@ async def main(): 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. -#### 4. Pattern Name +
+ +
+Pattern Name (only for subscribing) A pattern name contains wildcard `*` (matches any _single_ name segment) @@ -143,7 +154,11 @@ async def main(): 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. -#### Extra functions +
+ +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. @@ -186,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, @@ -197,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. @@ -219,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. @@ -239,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. @@ -255,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+.