spvirit-codec 0.1.2

PVAccess protocol encode/decode and connection state tracking.
Documentation

Spvirit

crates.io (spvirit-types) crates.io (spvirit-codec) crates.io (spvirit-tools) License

/ˈspɪrɪt/ of the Machine

Spvirit is a Rust library for working with EPICS PVAccess protocol, including encoding/decoding and connection state tracking. It also includes tools for monitoring and testing PVAccess connections. These are more proof of concept than production ready, but they are available for anyone to use and contribute to.

Why Rust?

Because why not, admittedly I just wanted to learn Rust and this seemed like a fun project with a moderately useful outcome.

Project Structure

The project is structured as a Cargo workspace with three crates:

  • spvirit-types: Contains shared data model types for PVAccess Normative Types (NT).
  • spvirit-codec: Contains the PVAccess protocol encoding/decoding logic and connection state tracking.
  • spvirit-tools: Contains command-line tools for monitoring and testing PVAccess connections.

In the future I would like to split out the tools to client and server tools and add some more IOC-like functionality to the server tools, but for now they are all in one crate for simplicity.

Getting Started

Install Rust

# Linux

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

clone the repo

git clone https://github.com/ISISNeutronMuon/spvirit

Build the project

cd spvirit

cargo build --release

Run the tools

cargo run --bin spvirit_monitor my:pv:name

# or

./target/release/spvirit_monitor my:pv:name

# or if installed 

cargo install spvirit-tools


spvirit_monitor my:pv:name

Using the library in your own Rust project

Add the crates you need to your Cargo.toml:

[dependencies]

spvirit-tools = "0.1"    # client/server library + CLI tools

spvirit-codec = "0.1"    # low-level PVA protocol encode/decode

spvirit-types = "0.1"    # shared Normative Type data model

Fetching a PV value (client)

use spvirit_tools::{format_output, pvget, PvGetOptions, RenderOptions};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pv_name = std::env::args()
        .nth(1)
        .unwrap_or_else(|| "MY:PV:NAME".into());

    let opts = PvGetOptions::new(pv_name);
    let result = pvget(&opts).await?;

    let render = RenderOptions::default();
    println!("{}", format_output(&result.pv_name, &result.value, &render));
    Ok(())
}

Searching for a PV server

use std::time::Duration;
use spvirit_tools::{build_auto_broadcast_targets, search_pv};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pv_name = std::env::args()
        .nth(1)
        .unwrap_or_else(|| "MY:PV:NAME".into());

    let targets = build_auto_broadcast_targets();

    let addr = search_pv(&pv_name, 5076, Duration::from_secs(5), &targets, false).await?;
    println!("Found server at {addr}");
    Ok(())
}

Decoding a raw PVA packet (codec)

use spvirit_codec::{PvaPacket, PvaPacketCommand};

fn main() {
    let raw: &[u8] = &[
        0xCA, 0x02, 0x00, 0x01, // header: magic, version, flags (LE), command 1
        0x09, 0x00, 0x00, 0x00, // payload length = 9
        0x00, 0x40, 0x00, 0x00, // buffer_size = 16384
        0xFF, 0x7F,             // introspection_registry_size = 32767
        0x00, 0x00,             // qos = 0
        0x00,                   // authz = "" (empty string)
    ];

    let mut packet = PvaPacket::new(raw);

    println!("command: {}", packet.header.command);
    println!("endian:  {}", if packet.header.flags.is_msb { "big" } else { "little" });

    if let Some(cmd) = packet.decode_payload() {
        match cmd {
            PvaPacketCommand::ConnectionValidation(cv) => {
                println!("buffer_size: {}", cv.buffer_size);
            }
            other => println!("{other:?}"),
        }
    }
}

See the examples/ folders for runnable versions of each snippet:

cargo run --example pvget_example -p spvirit-tools       # requires a running IOC

cargo run --example search_example -p spvirit-tools      # requires a running IOC

cargo run --example decode_packet -p spvirit-codec       # self-contained, no IOC needed

Tools available

spvirit tool EPICS Base equivalent Description
spvirit_get pvget Fetch the current value of a PV
spvirit_put pvput Write a value to a PV
spvirit_monitor pvmonitor Subscribe to a PV and print value changes
spvirit_info pvinfo Display field/metadata information for a PV
spvirit_list pvlist List all available PVs on discovered servers
spvirit_server softIoc Not fully one-to-one - just a demo, it does parse some db file vocab
spvirit_explore Interactive TUI to browse servers, select PVs, and monitor values
spvirit_search TUI showing PV search network traffic for diagnostics
spvirit_sine Continuously write a sine wave to a PV (demo/testing)
spvirit_dodeca Server publishing a rotating 3D dodecahedron as an NTNDArray PV

Related Projects

  • spvirit-scry — A Rust tool for capturing and analyzing pvAccess EPICS packets.

References

I used the following libraries and repos as refernce materials for PVAccess protocol: