diff --git a/src/pycyphal2/__init__.py b/src/pycyphal2/__init__.py index 88043d9b..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,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: + +
+Relative Name + +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` | + +
+ +
+Absolute Name + +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` | + +
+ +
+Homeful Name + +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. + +
+ +
+Pattern Name (only for subscribing) + +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. + +
+ +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 `~/`. @@ -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, @@ -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. @@ -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. @@ -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. @@ -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+.