Skip to main content

Simple CLI tool for diagnostics and debugging of Cyphal networks.

Project description

Yakut

OpenCyphal logo

Build status PyPI - Version Code style: black Forum

Yakút is a simple cross-platform command-line interface (CLI) tool for diagnostics and debugging of Cyphal networks. By virtue of being based on PyCyphal, Yakut supports all Cyphal transports (UDP, serial, CAN, ...) and is compatible with all major features of the protocol. It is designed to be usable with GNU/Linux, Windows, and macOS.

yakut monitor

Ask questions and get assistance at forum.opencyphal.org.

Installing

First, make sure to have Python installed. Windows users are recommended to grab the official distribution from Windows Store.

Install Yakut: pip install yakut

By default, Yakut does not support joysticks or MIDI controllers (this feature is described below in section Publishing messages). To enable the support for input devices, install the optional dependency: pip install yakut[joystick]. GNU/Linux users will need to also install: SDL2, possibly libjack (with headers), possibly libasound2 (with headers) (if you are using a Debian-based distro, the required packages are: libsdl2-dev libasound2-dev libjack-dev).

Afterward do endeavor to read the docs: yakut --help

Check for new versions every now and then: pip install --upgrade yakut

Installation & configuration screencasts are available for Windows, GNU/Linux, and macOS.

Additional third-party tools

  • Cyphal/CAN on GNU/Linux: can-utils
  • Cyphal/UDP or Cyphal/CAN: Wireshark (n.b.: Wireshark might label Cyphal captures as UAVCAN due to rebranding)

Invoking commands

Any option can be supplied either as a command-line argument or as an environment variable named like YAKUT_[subcommand_]option. If both are provided, command-line options take precedence over environment variables. You can use this feature to configure desired defaults by exporting environment variables from the rc-file of your shell (for bash/zsh this is ~/.bashrc/~/.zshrc, for PowerShell see $profile).

Options for the main command shall be specified before the subcommand when invoking Yakut:

yakut --path=/the/path compile path/to/my_namespace --output=destination/directory

In this example, the corresponding environment variables are YAKUT_PATH and YAKUT_COMPILE_OUTPUT.

There is a dedicated --help option for every subcommand.

Yakut may also be invoked via its alias y as long as this name does not conflict with another installed program.

Compiling DSDL

Suppose we have our custom DSDL namespace that we want to use. First, it needs to be compiled:

yakut compile ~/custom_data_types/sirius_cyber_corp

Most of the commands require the standard namespace to be available, so let's compile it too, along with the regulated namespace:

yakut compile  ~/public_regulated_data_types/uavcan  ~/public_regulated_data_types/reg

Yakut can directly fetch archives containing DSDL namespace directories at the top level, too:

# Compile all DSDL namespaces found in the archives:
yakut compile \
    https://github.com/OpenCyphal/public_regulated_data_types/archive/refs/heads/master.zip \
    https://github.com/Zubax/zubax_dsdl/archive/refs/heads/master.zip

Compilation outputs will be stored in the current working directory, but it can be overridden if needed via --output or YAKUT_COMPILE_OUTPUT. Naturally, Yakut needs to know where the outputs are located to use them; by default it looks in the current directory. You can specify additional search locations using --path or YAKUT_PATH.

A question one is likely to ask here is: Why don't you ship precompiled regulated DSDL together with the tool? Indeed, that would be trivial to do, but we avoid that on purpose to emphasize our commitment to supporting vendor-specific and regulated DSDL at the same level. In the past we used to give regulated namespaces special treatment, which caused our users to acquire misconceptions about the purpose of DSDL. Specifically, there have been forks of the standard namespace extended with vendor-specific types, which is harmful to the ecosystem.

Having to manually compile the regulated namespaces is not an issue because it is just a single command to run. You may opt to keeping compiled namespaces that you use often somewhere in a dedicated directory and put YAKUT_PATH=/your/directory into your shell's rc-file so that you don't have to manually specify the path when invoking Yakut. Similarly, you can configure it to use that directory as the default destination for compiled DSDL:

# bash/zsh on GNU/Linux or macOS
export YAKUT_COMPILE_OUTPUT=~/.yakut
export YAKUT_PATH="$YAKUT_COMPILE_OUTPUT"
# PowerShell on Windows (double quotes are always required!)
$env:YAKUT_COMPILE_OUTPUT="$env:APPDATA\Yakut"
$env:YAKUT_PATH="$env:YAKUT_COMPILE_OUTPUT"

So that you say simply yakut compile path/to/my_namespace knowing that the outputs will be always stored to and read from a fixed place unless you override it.

Communicating

Commands that access the network need to know how to do so. This is typically configured via standard Cyphal registers assigned from environment variables.

Cyphal registers are named values that contain various configuration parameters of a Cyphal application/node. They are extensively described in the Cyphal Specification. When starting a new process, it is possible to pass arbitrary registers via environment variables.

There are certain registers that are looked at by Cyphal nodes to determine how to connect to the network. Some of them are given below, but the list is not exhaustive. The full description of supported registers is available in the API documentation for pycyphal.application.make_transport().

If the available registers define more than one transport configuration, a redundant transport will be initialized. It is not necessary to assign all of these registers to use a particular transport because all of them except uavcan.*.iface come with defaults.

Transport Register name Register type Environment variable name Semantics Example environment variable value
(any) uavcan.node.id natural16[1] UAVCAN__NODE__ID The local node-ID; anonymous if not set 42
UDP uavcan.udp.iface string UAVCAN__UDP__IFACE Space-separated local IPs (16 LSB overridden with node-ID) 127.9.0.0 192.168.0.0
Serial uavcan.serial.iface string UAVCAN__SERIAL__IFACE Space-separated serial port names COM9 socket://127.0.0.1:50905
CAN uavcan.can.iface string UAVCAN__CAN__IFACE Space-separated CAN iface names socketcan:vcan0 pcan:PCAN_USBBUS1
CAN uavcan.can.mtu natural16[1] UAVCAN__CAN__MTU Maximum transmission unit; selects Classic/FD 64
CAN uavcan.can.bitrate natural32[2] UAVCAN__CAN__BITRATE Arbitration/data segment bits per second 1000000 4000000
Loopback uavcan.loopback bit[1] UAVCAN__LOOPBACK Use loopback interface (only for basic testing) 1

Protip on environment variables

Defining the required environment variables manually is unergonomic and time-consuming. A better option is to have relevant configuration that you use often defined in a dedicated file (or several) that is sourced into the current shell session as necessary (conceptually this is similar to virtual environments used in Python, etc). Here is an example for a doubly-redundant CAN bus (assuming sh/bash/zsh here):

# Common Cyphal register configuration for testing & debugging.
# Source this file into your sh/bash/zsh session before using Yakut and other Cyphal tools.
# You can also insert automatic iface initialization here, e.g., by checking if /sys/class/net/slcan0 exists.
export UAVCAN__CAN__IFACE='socketcan:slcan0 socketcan:slcan1'
export UAVCAN__CAN__MTU=8
export UAVCAN__NODE__ID=$(yakut accommodate)  # Pick an unoccupied node-ID automatically for this shell session.
echo "Auto-selected node-ID for this session: $UAVCAN__NODE__ID"

Usage:

$ . my_environment.sh
$ yakut monitor  # Whatever.

Subscribing to subjects

Subscribe to subject 33 of type uavcan.si.unit.angle.Scalar as shown below; notice how we specify the subject-ID before the data type name. If the data type version number(s) are not specified (minor or both), the latest available is chosen automatically. You will see output if/when there is a publisher on this subject (more on this in the next section).

$ export UAVCAN__UDP__IFACE=127.63.0.0
$ yakut sub 33:uavcan.si.unit.angle.Scalar --with-metadata
---
33:
  _metadata_:
    timestamp: {system: 1608987583.298886, monotonic: 788272.540747}
    priority: nominal
    transfer_id: 0
    source_node_id: 42
  radian: 2.309999942779541

---
33:
  _metadata_:
    timestamp: {system: 1608987583.298886, monotonic: 788272.540747}
    priority: nominal
    transfer_id: 1
    source_node_id: 42
  radian: 2.309999942779541

If more than one subject is specified, a synchronizer will be used to group messages from multiple subjects into synchronized groups, which are then printed all at once. If subjects are not updated in lockstep some or all messages may be dropped.

subject synchronization

Yakut can determine the data type names automatically if the publisher node(s) support the required network introspection services. In the following example only the subject-IDs are provided and the type information is discovered automatically:

$ y sub 100 110 120 140 150
---
100:
  heartbeat:
    readiness: {value: 3}
    health: {value: 0}
  demand_factor_pct: 2
110:
  timestamp: {microsecond: 1650748118444258}
  value:
    kinematics:
      angular_position: {radian: 5.42976188659668}
      angular_velocity: {radian_per_second: 111.61508178710938}
      angular_acceleration: {radian_per_second_per_second: -0.10802359879016876}
    torque: {newton_meter: 0.012790549546480179}
120:
  timestamp: {microsecond: 1650748118444258}
  value:
    current: {ampere: 0.055703502148389816}
    voltage: {volt: 24.92441749572754}
140: {dc_voltage: 125, dc_current: 0, phase_current_amplitude: 4, velocity: 111, ratiometric_setpoint: 9}
150:
  current: [0.0020294189453125, 0.70458984375]
  voltage: [0.1893310546875, 1.3359375]

Use --help to see additional options (--redraw is often useful).

Exporting data to computer algebra systems or spreadsheet processors

Here the reg.udral.physics.dynamics.rotation.PlanarTs message is formatted using the TSV formatter with headers prepended. The resulting data can be imported as-is into Excel, Wolfram Mathematica, etc.

yakut --format=TSVH subscribe 142:reg.udral.physics.dynamics.rotation.PlanarTs > rotation_data.tsv

Publishing messages

Publishing two messages synchronously twice (four messages total):

export UAVCAN__UDP__IFACE=127.63.0.0
export UAVCAN__NODE__ID=42
yakut pub -N2 33:uavcan.si.unit.angle.Scalar 2.31 uavcan.diagnostic.Record '{text: "2.31 radian"}'

We did not specify the subject-ID for the second subject, so Yakut defaulted to the fixed subject-ID. Like in the case of subscriber, automatic subject type discovery is also available here.

The above example will publish constant values which is rarely useful. You can define arbitrary Python expressions that are evaluated by Yakut before every publication. Such expressions are entered as strings marked with a YAML tag !$. There may be an arbitrary number of such expressions in a YAML document, and their results may be arbitrary as long as the final structure can initialize the specified message. The following example will publish a sinewave with frequency 1 Hz, amplitude 10 meters:

yakut pub -T 0.01 1234:uavcan.si.unit.length.Scalar '{meter: !$ "sin(t * pi * 2) * 10"}'

Notice that we make use of entities like the variable t or the standard function sin in the expression. You will see the full list of available entities if you run y pub --help.

One particularly important capability of this command is the ability to read data from connected joysticks or MIDI controllers. It allows the user to control distributed processes or equipment in real time, simulate sensor feeds, etc. Function A(x,y) returns the normalized value of axis y from connected controller x (for full details see yakut pub --help); likewise, there is B(x,y) for push buttons and T(x,y) for toggle switches. The next example will publish 3D angular velocity setpoint, thrust setpoint, and the arming switch state, allowing the user to control these parameters interactively:

yakut pub -T 0.1 \
    5:uavcan.si.unit.angular_velocity.Vector3 '!$ "[A(1,0)*10, A(1,1)*10, (A(1,2)-A(1,5))*5]"' \
    6:uavcan.si.unit.power.Scalar '!$ A(2,10)*1e3' \
    7:uavcan.primitive.scalar.Bit '!$ T(1,5)'

To see the published values, either launch a subscriber in a new terminal as y sub 5 6 7, or add --verbose.

Observe that we didn't spell out the field names here (radian_per_second, watt, value) because it is actually not required; information on the accepted formats can be found in the documentation in PyCyphal API for pycyphal.dsdl.update_from_builtin().

The list of connected controllers and how their axes are mapped can be seen using yakut joystick, as shown in the video:

yakut joystick

Here is an example where a MIDI controller is used to interactively change the frequency and amplitude of a sinewave:

yakut publish

Invoking RPC-services

Given custom data types:

# sirius_cyber_corp.PerformLinearLeastSquaresFit.1.0
PointXY.1.0[<64] points
@extent 1024 * 8
---
float64 slope
float64 y_intercept
@sealed
# sirius_cyber_corp.PointXY.1.0
float16 x
float16 y
@sealed

Suppose that there is node 42 that serves sirius_cyber_corp.PerformLinearLeastSquaresFit.1.0 at service-ID 123:

$ export UAVCAN__UDP__IFACE=127.63.0.0
$ export UAVCAN__NODE__ID=42
$ yakut compile sirius_cyber_corp ~/public_regulated_data_types/uavcan
$ yakut call 42 123:sirius_cyber_corp.PerformLinearLeastSquaresFit 'points: [{x: 10, y: 1}, {x: 20, y: 2}]'
---
123:
  slope: 0.1
  y_intercept: 0.0

You might notice that the verbose initialization form used in this example is hard to type: points: [{x: 10, y: 1}, {x: 20, y: 2}]. Instead, you can use positional initialization for convenience: [[10, 1], [20, 2]].

Automatic data type discovery is also available here but the service has to be referred by name, not port-ID:

y q 42 least_squares '[[10, 1], [20, 2]]'  # "y q" is shorthand for "yakut call" (see --help for more)

You can still override the type if you want to use a different one (e.g., if the remote node names the type of this service differently):

y q 42 least_squares:my_namespace.MySpecialType '[[10, 1], [20, 2]]'

Monitoring the network

The command yakut monitor can be used to display all activity on the network in a compact representation. It tracks online nodes and maintains real-time statistics on all transfers exchanged between each node on the network. It may also be able to detect some common network configuration issues like zombie nodes (nodes that do not publish uavcan.node.Heartbeat).

Read yakut monitor --help for details.

$ export UAVCAN__CAN__IFACE="socketcan:can0 socketcan:can1 socketcan:can2"  # Triply-redundant Cyphal/CAN
$ export UAVCAN__CAN__MTU=8                     # Force MTU = 8 bytes
$ export UAVCAN__CAN__BITRATE="1000000 1000000" # Disable BRS, use the same bit rate for arbitration/data
$ y mon                                         # "y mon" is shorthand for "yakut monitor"
yakut monitor

The monitor can be an anonymous node or it can be given a node-ID of its own. In the latter case it will actively query other nodes using the standard introspection services.

Some transports, Cyphal/UDP in particular, require elevated privileges to run this tool due to the security implications of low-level packet capture.

Updating node software

The file server command can be used to serve files, run a plug-and-play node-ID allocator (some embedded bootloader implementations require that), and automatically send software update requests uavcan.node.ExecuteCommand to nodes whose software is old.

To demonstrate this capability, suppose that the network contains the following nodes:

  • nodes 1, 2 named com.example.foo, software 1.0
  • nodes 3, 4 named com.example.bar, hardware v4.2, software v3.4
  • node 5 named com.example.baz

Software updates are distributed as atomic package files. In case of embedded systems, the package is usually just the firmware image, possibly compressed or amended with some metadata. For the file server this is irrelevant since it never looks inside the files it serves. However, the name is relevant as it shall follow a particular pattern to make the server recognize the file as a software package. The full specification is given in the command help: yakut file-server --help.

Suppose that we have the following packages that we need to deploy:

  • v1.1 for nodes com.example.foo with any hardware
  • v3.3 for nodes com.example.bar with hardware v4.x
  • v3.5 for nodes com.example.bar with hardware v5.6 only
  • nothing for com.example.baz
$ ls *.app*                       # List all software packages
com.example.foo-1.1.app.zip       # Any hardware
com.example.bar-4-3.3.app.pkg     # Hardware v4.x
com.example.bar-5.6-3.5.app.bin   # Hardware v5.6 only

The server rescans its root directory whenever a new node is found online, meaning that packages can be added/removed at runtime and the server will pick up the changes on the fly. Launch the server:

$ export UAVCAN__UDP__IFACE=127.63.0.0
$ export UAVCAN__NODE__ID=42
$ yakut file-server --plug-and-play=allocation_table.db --update-software

If there are any nodes online (or if they join the network later), the server will check the version of each by sending uavcan.node.GetInfo, and if a newer package is available locally, it will request the node to install it by sending uavcan.node.ExecuteCommand.

In this specific case, the following will happen:

  • Nodes 1 and 2 will be updated to v1.1.
  • Nodes 3 and 4 will not be updated because the newer package v3.5 is incompatible with hardware v4.2, and the compatible version v3.3 is too old.
  • Node 5 will not be updated because there are no suitable packages.

Add --verbose to see how exactly the decisions are made.

This command can be used to implement automatic network-wide configuration management. Start the server and leave it running. Store all relevant packages into its root directory. When a node is connected or restarted, the server will automatically compare the version of its software against the local files and perform an update if necessary. Therefore, the entire network will be kept up-to-date without manual intervention.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

yakut-0.10.0.tar.gz (97.8 kB view hashes)

Uploaded Source

Built Distribution

yakut-0.10.0-py3-none-any.whl (114.4 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page