spvirit-codec 0.1.4

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 not yet production ready , but they are available for anyone to use and contribute to.

Key areas of development in the near future include:

  • More complete support for EPICS Normative Types (NT) and their associated metadata.
  • softIOC-like server functionality, including parsing of EPICS db files and support for more complex PV types and behaviors, a prototype of which is already available in the spserver tool.

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 spmonitor my:pv:name

# or

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

# or if installed 

cargo install spvirit-tools


spmonitor 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 MY:PV:NAME      # requires a running IOC

cargo run --example search_example -p spvirit-tools MY:PV:NAME     # 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
spget pvget Fetch the current value of a PV
spput pvput Write a value to a PV
spmonitor pvmonitor Subscribe to a PV and print value changes
spinfo pvinfo Display field/metadata information for a PV
splist pvlist List all available PVs on discovered servers
spserver softIoc Not fully one-to-one - just a demo, it does parse some db file vocab
spexplore Interactive TUI to browse servers, select PVs, and monitor values
spsearch TUI showing PV search network traffic for diagnostics
spsine Continuously write a sine wave to a PV (demo/testing)
spdodeca Server publishing a rotating 3D dodecahedron as an NTNDArray PV

Server (softIOC like experiment)

While not a full softIOC implementation, spserver is a simple PVAccess server that can serve some static PVs and parse a limited subset of EPICS db file syntax. It proves that the encoding/decoding and connection handling logic in spvirit-codec is sufficient to implement a server, and it can be used as a starting for a more full featured softIOC in the future. (hint hint PRs welcome :))

Integration test matrix

I have tested the tools in this repo against the following EPICS PVAccess servers:

  • EPICS
  • p4p (pvxs under the hood)
  • PvAccessJava

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:

GenAI Usage Log

Section / Area What Was Done With AI Plans Ahead
spvirit-types Hand coded, few types completed with AI, the prettified with AI keep the same, fairly complete
spvirit-codec Most was hand-coded, some restructuring and prettifying was done with AI. keep the same, bring in any common helpers, maybe write a siplified API for users
spvirit-tools Mostly AI generated, manually coded parts of Put an Get then let the Agents build on top. Need to split it out into spvirit-IOC/server/client utils and the like to idiomatically conform to EPICS-base/pvxs client/servers. They do work and are ineter-op tested against other EPICS PVA implementations in C++/Java but don't have super clean/intuitive API's at the moment.
Testing I wrote some basic tests, then used GenAI agents to generate more tests and test cases, which I then manually curated and edited. Suite is fairly comprehensive so I will keep it as is.