# Spvirit
[](https://crates.io/crates/spvirit-types)
[](https://crates.io/crates/spvirit-codec)
[](https://crates.io/crates/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
``` bash
# Linux
### clone the repo
``` bash
git clone https://github.com/ISISNeutronMuon/spvirit
```
### Build the project
``` bash
cd spvirit
cargo build --release
```
### Run the tools
``` bash
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`:
```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)
```rust
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
```rust
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)
```rust
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/`](spvirit-tools/examples) folders for runnable versions of each snippet:
```bash
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
| `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](https://crates.io/crates/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:
- [pvxs](https://epics-base.github.io/pvxs/)
- [pvAccess Protocol Specification](https://docs.epics-controls.org/en/latest/pv-access/protocol.html)
- [EPICS Base](https://github.com/epics-base/epics-base)
- [PVAshark](https://github.com/george-mcintyre/pvashark)
## GenAI Usage Log
| `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. |