| Crates.io | pvxs-sys |
| lib.rs | pvxs-sys |
| version | 0.1.0 |
| created_at | 2025-12-14 14:40:35.517099+00 |
| updated_at | 2025-12-14 14:40:35.517099+00 |
| description | Low-level FFI bindings for EPICS PVXS library |
| homepage | https://github.com/ctrl-sys-ui/pvxs-sys |
| repository | https://github.com/ctrl-sys-ui/pvxs-sys |
| max_upload_size | |
| id | 1984570 |
| size | 419,302 |
Complete low-level FFI bindings for the EPICS PVXS (PVAccess) library.
Note: This is a
-syscrate providing raw FFI bindings. For a high-level, idiomatic Rust API, use theepics-pvxscrate (coming soon).
This crate provides safe Rust bindings around the PVXS C++ library using the cxx crate. PVXS implements the PVAccess network protocol used in EPICS (Experimental Physics and Industrial Control System).
π Production-ready EPICS server and client implementation! Create EPICS servers and clients with full network discovery, rich metadata support, array operations, and real-time monitoring.
cxx crateThis is a -sys crate that provides low-level FFI bindings to the PVXS C++ library using cxx, wrapped with a safe and ergonomic Rust API suitable for direct use.
This crate provides a complete EPICS PVXS implementation with separate client and server capabilities:
cxx cratecloneEmpty() for updatesclient_wrapper.cpp and server_wrapper.cpp implementationswrapper.h for both client and server functionalitycxx-bridgeBefore using this crate, you need:
Set the following environment variables:
EPICS_BASE - Path to your EPICS base installation (required)EPICS_HOST_ARCH - Your host architecture (auto-detected if not set)
linux-x86_64, windows-x64, darwin-x86EPICS_PVXS - Path to PVXS installation (required)
PVXS_DIR or PVXS_BASE as alternativesEPICS_PVXS_LIBEVENT - Path to libevent installation (optional)
{PVXS}/bundle/usr/{ARCH}event_core.dllExample setup:
# Linux
export EPICS_BASE=/opt/epics/base
export EPICS_HOST_ARCH=linux-x86_64
export EPICS_PVXS=/opt/epics/modules/pvxs
# Optional: export EPICS_PVXS_LIBEVENT=/opt/epics/modules/pvxs/bundle/usr/linux-x86_64
# Windows (PowerShell)
$env:EPICS_BASE = "C:\epics\base"
$env:EPICS_HOST_ARCH = "windows-x64"
$env:EPICS_PVXS = "C:\epics\pvxs"
# Optional: $env:EPICS_PVXS_LIBEVENT = "C:\epics\pvxs\bundle\usr\windows-x64"
Add this to your Cargo.toml:
[dependencies]
pvxs-sys = "0.1"
# For async support
pvxs-sys = { version = "0.1", features = ["async"] }
async - Enables async/await support using Tokio
get_async(), put_double_async(), and info_async() methodscargo run --features async --example async_operationsFor Windows users, the following DLLs are automatically copied by the build script to target/debug and target/release:
pvxs.dll from {EPICS_PVXS}\bin\{EPICS_HOST_ARCH}Com.dll from {EPICS_BASE}\bin\{EPICS_HOST_ARCH}event_core.dll from {EPICS_PVXS}\bundle\usr\{EPICS_HOST_ARCH}\libNo manual PATH configuration is needed for running examples or tests.
use pvxs_sys::{Context, PvxsError};
fn main() -> Result<(), PvxsError> {
// Create context from environment variables
let mut ctx = Context::from_env()?;
// Read a PV value with 5 second timeout
let value = ctx.get("TEST:DOUBLE", 5.0)?;
// Access the main value field
let v = value.get_field_double("value")?;
println!("Value: {}", v);
Ok(())
}
use pvxs_sys::{Context, PvxsError};
fn main() -> Result<(), PvxsError> {
let mut ctx = Context::from_env()?;
// Write scalar values
ctx.put_double("TEST:DOUBLE", 42.0, 5.0)?;
ctx.put_int32("TEST:INT", 123, 5.0)?;
ctx.put_string("TEST:STRING", "Hello", 5.0)?;
// Write array values
ctx.put_double_array("TEST:ARRAY", vec![1.0, 2.0, 3.0], 5.0)?;
ctx.put_int32_array("TEST:INT_ARRAY", vec![10, 20, 30], 5.0)?;
println!("Values written successfully!");
Ok(())
}
use pvxs_sys::{Context, PvxsError};
fn main() -> Result<(), PvxsError> {
let mut ctx = Context::from_env()?;
// Create and start a monitor
let mut monitor = ctx.monitor("TEST:COUNTER")?;
monitor.start()?;
// Poll for updates
for _ in 0..10 {
match monitor.get_update(5.0) {
Ok(value) => {
let v = value.get_field_double("value")?;
println!("New value: {}", v);
}
Err(e) => eprintln!("Monitor error: {}", e),
}
}
monitor.stop()?;
Ok(())
}
use pvxs_sys::{Server, NTScalarMetadataBuilder, DisplayMetadata, PvxsError};
use std::thread;
use std::time::Duration;
fn main() -> Result<(), PvxsError> {
// Create server from environment (enables network discovery)
let mut server = Server::from_env()?;
// Create PV with rich metadata
let metadata = NTScalarMetadataBuilder::new()
.alarm(0, 0, "OK")
.display(DisplayMetadata {
limit_low: 0,
limit_high: 100,
description: "Temperature sensor".to_string(),
units: "DegC".to_string(),
precision: 2,
});
let mut temp_pv = server.create_pv_double("temp:sensor1", 23.5, metadata)?;
// Start server
server.start()?;
println!("Server running - PV available at: temp:sensor1");
// Update values periodically
for i in 0..100 {
let new_temp = 23.5 + (i as f64 * 0.1);
temp_pv.post_double(new_temp)?;
thread::sleep(Duration::from_secs(1));
}
Ok(())
}
# Windows - Make sure environment variables are set
$env:EPICS_BASE = "C:\epics\base"
$env:EPICS_HOST_ARCH = "windows-x64"
$env:EPICS_PVXS = "C:\epics\pvxs"
# Build the library
cargo build
# Run tests (requires EPICS environment)
cargo test
# Linux/macOS - Make sure environment variables are set
export EPICS_BASE=/path/to/epics/base
export EPICS_HOST_ARCH=linux-x86_64
export EPICS_PVXS=/path/to/pvxs
# Build the library
cargo build
# Windows - Run the metadata server example
cargo run --example metadata_server
# Test from another terminal using EPICS pvget/pvinfo
pvget temperature:sensor1
pvinfo temperature:sensor1
This repository includes comprehensive examples demonstrating all major features:
metadata_server.rs - Complete EPICS server with rich NTScalar metadata (display, control, alarms)Run the metadata server example:
cargo run --example metadata_server
# In another terminal, test the PV:
pvget temperature:sensor1
pvinfo temperature:sensor1 # See full metadata structure
The crate includes an extensive test suite covering all functionality:
# Run all tests
cargo test
# Run specific test categories
cargo test test_client # Client operations
cargo test test_server # Server operations
cargo test test_monitor # Monitor functionality
cargo test test_value # Value operations
cargo test test_arrays # Array operations
Note: Tests create isolated servers and do not require external IOCs.
This repository includes comprehensive examples demonstrating all major features:
metadata_server.rs - Complete EPICS server with rich NTScalar metadata (display, control, alarms)Run the metadata server example:
cargo run --example metadata_server
# In another terminal, test the PV:
pvget temperature:sensor1
pvinfo temperature:sensor1 # See full metadata structure
The crate includes an extensive test suite covering all functionality:
# Run all tests
cargo test
# Run specific test categories
cargo test test_client # Client operations
cargo test test_server # Server operations
cargo test test_monitor # Monitor functionality
cargo test test_value # Value operations
cargo test test_arrays # Array operations
Note: Tests create isolated servers and do not require external IOCs.
pvxs-sys/
βββ build.rs # Build script (C++ compilation, C++17)
βββ Cargo.toml # Rust package manifest
βββ build-pvxs-only.ps1 # Automated PVXS build script for Windows
βββ BUILDING_PVXS_WINDOWS.md # Detailed Windows build guide
βββ include/
β βββ wrapper.h # C++ wrapper header (shared by client & server)
βββ src/
β βββ lib.rs # Main Rust API (safe, idiomatic)
β βββ bridge.rs # CXX bridge definitions
β βββ client_wrapper.cpp # C++ client wrapper (GET/PUT/INFO)
β βββ client_wrapper_async.cpp # C++ async operations wrapper
β βββ client_wrapper_monitor.cpp # C++ monitor/subscription wrapper
β βββ client_wrapper_rpc.cpp # C++ RPC wrapper
β βββ server_wrapper.cpp # C++ server wrapper (Server/SharedPV/StaticSource)
βββ examples/
β βββ metadata_server.rs # Server with full NTScalar metadata
βββ tests/ # Comprehensive test suite
β βββ test_client_context_*.rs # Client operation tests
β βββ test_server_*.rs # Server tests
β βββ test_monitor_*.rs # Monitor tests
β βββ test_value*.rs # Value and array tests
β βββ test_integration_*.rs # Integration tests
βββ README.md # This file
// Context - Main client entry point
let mut ctx = Context::from_env()?;
// GET operations
let value = ctx.get("PV:NAME", timeout)?;
let v = value.get_field_double("value")?;
// PUT operations (scalars)
ctx.put_double("PV:NAME", 42.0, timeout)?;
ctx.put_int32("PV:NAME", 123, timeout)?;
ctx.put_string("PV:NAME", "text", timeout)?;
ctx.put_enum("PV:NAME", 2, timeout)?;
// PUT operations (arrays)
ctx.put_double_array("PV:NAME", vec![1.0, 2.0, 3.0], timeout)?;
ctx.put_int32_array("PV:NAME", vec![10, 20, 30], timeout)?;
ctx.put_string_array("PV:NAME", vec!["a".to_string(), "b".to_string()], timeout)?;
// INFO operations
let info = ctx.info("PV:NAME", timeout)?;
// Monitor operations - Basic usage with get_update()
let mut monitor = ctx.monitor("PV:NAME")?;
monitor.start()?;
let update = monitor.get_update(timeout)?; // Blocking, waits for data
monitor.stop()?;
// Monitor operations - Advanced with MonitorBuilder
let mut monitor = ctx.monitor_builder("PV:NAME")?
.connect_exception(true) // Throw exception on connection events
.disconnect_exception(true) // Throw exception on disconnection events
.exec()?;
monitor.start()?;
// Using pop() - Non-blocking, returns immediately
use pvxs_sys::MonitorEvent;
loop {
match monitor.pop() {
Ok(Some(value)) => {
// Got data update
println!("Value: {}", value.get_field_double("value")?);
}
Ok(None) => {
// Queue empty, no data available
break;
}
Err(MonitorEvent::Connected(msg)) => {
// Connection event (when connect_exception(true))
println!("Connected: {}", msg);
}
Err(MonitorEvent::Disconnected(msg)) => {
// Disconnection event (when disconnect_exception(true))
println!("Disconnected: {}", msg);
}
Err(MonitorEvent::Finished(msg)) => {
// Monitor finished/closed
println!("Finished: {}", msg);
break;
}
}
}
// Monitor with C-style callback
extern "C" fn my_callback() {
println!("Monitor event occurred!");
}
let mut monitor = ctx.monitor_builder("PV:NAME")?
.connect_exception(false) // Queue connection events as data
.disconnect_exception(false) // Queue disconnection events as data
.event(my_callback) // Set callback function
.exec()?;
monitor.start()?;
// Callback is invoked automatically when events occur
// Create server
let mut server = Server::from_env()?; // Network-enabled
let mut server = Server::create_isolated()?; // Local-only
// Create PVs with metadata
let metadata = NTScalarMetadataBuilder::new()
.alarm(severity, status, "message")
.display(DisplayMetadata { ... })
.control(ControlMetadata { ... })
.value_alarm(ValueAlarmMetadata { ... });
// Scalar PVs
let mut pv1 = server.create_pv_double("name", 42.0, metadata)?;
let mut pv2 = server.create_pv_int32("name", 123, metadata)?;
let mut pv3 = server.create_pv_string("name", "text", metadata)?;
// Array PVs
let mut pv4 = server.create_pv_double_array("name", vec![1.0, 2.0], metadata)?;
let mut pv5 = server.create_pv_int32_array("name", vec![10, 20], metadata)?;
let mut pv6 = server.create_pv_string_array("name", vec!["a".to_string()], metadata)?;
// StaticSource - organize PVs into groups
let mut source = StaticSource::create()?;
source.add_pv("device:pv1", &mut pv1)?;
server.add_source("static", &mut source, priority)?;
// Server lifecycle
server.start()?;
let port = server.tcp_port();
server.stop()?;
// Update PV values
pv1.post_double(99.9)?;
pv2.post_int32(456)?;
pv3.post_string("updated")?;
// Access scalar fields
let d = value.get_field_double("value")?;
let i = value.get_field_int32("value")?;
let s = value.get_field_string("value")?;
let e = value.get_field_enum("value")?;
// Access array fields
let da = value.get_field_double_array("value")?;
let ia = value.get_field_int32_array("value")?;
let sa = value.get_field_string_array("value")?;
// Access alarm information
let severity = value.get_field_int32("alarm.severity")?;
let status = value.get_field_int32("alarm.status")?;
let message = value.get_field_string("alarm.message")?;
// Display value structure
println!("{}", value); // Pretty-print entire structure
The Monitor API provides real-time PV change notifications with flexible event handling:
use pvxs_sys::{Context, MonitorEvent};
// 3. Event-driven with callbacks
extern "C" fn on_monitor_event() {
println!("Monitor event detected!");
}
// 1. Simple monitoring with get_update() - Blocking
let mut monitor = ctx.monitor("PV:NAME")?;
monitor.start()?;
let value = monitor.get_update(5.0)?; // Wait up to 5 seconds
monitor.stop()?;
// 2. Non-blocking with pop() - Returns immediately
let mut monitor = ctx.monitor_builder("PV:NAME")?
.connect_exception(true) // Enable connection exceptions
.disconnect_exception(true) // Enable disconnection exceptions
.event(on_monitor_event) // Register callback
.exec()?;
// 3. Exception masking behavior
// connect_exception(true) -> Connection events throw MonitorEvent::Connected
// connect_exception(false) -> Connection events queued as normal data
// disconnect_exception(true) -> Disconnection events throw MonitorEvent::Disconnected
// disconnect_exception(false) -> Disconnection events queued as normal data
// 4. Registered callback
// Callback invoked automatically when data arrives
monitor.start()?;
loop {
match monitor.pop() {
Ok(Some(value)) => {
// New data available
println!("Got update: {}", value.get_field_double("value")?);
}
Ok(None) => {
// Queue is empty, no data right now
std::thread::sleep(std::time::Duration::from_millis(100));
continue;
}
Err(MonitorEvent::Connected(msg)) => {
println!("PV Connected: {}", msg);
}
Err(MonitorEvent::Disconnected(msg)) => {
println!("PV Disconnected: {}", msg);
}
Err(MonitorEvent::Finished(msg)) => {
println!("Monitor finished: {}", msg);
break;
}
}
}
monitor.stop()?;
Monitor Methods:
start() - Begin monitoring (enables event flow)stop() - Stop monitoring (disables event flow)get_update(timeout) - Blocking wait for next update (convenience method)pop() - Non-blocking check for updates (returns Result<Option<Value>, MonitorEvent>)MonitorEvent Exceptions:
Connected(String) - Connection established (when connect_exception(true))Disconnected(String) - Connection lost (when disconnect_exception(true))Finished(String) - Monitor closed/finishedCallback Signature:
extern "C" fn callback() {
// Called from PVXS worker thread
// Keep processing minimal - no blocking operations
}
The crate uses a four-layer architecture with modular client/server separation optimized for C++17:
βββββββββββββββββββββββββββββββββββββββ
β Rust API (src/lib.rs) β β Safe, idiomatic Rust
β - Context, Server, Value β High-level abstractions
β - Result<T, E>, PvxsError β Ergonomic error handling
β - NTScalarMetadataBuilder β Builder patterns
βββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββ
β CXX Bridge (src/bridge.rs) β β Type-safe FFI boundary
β - Opaque C++ types β Zero-cost abstractions
β - Shared structs (metadata) β Shared data structures
β - Function declarations β C++17 features exposed
βββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββ¬βββββββββββββββββββ
β Client Layer β Server Layer β β Parallel C++ adapters
β (4 cpp files) β (server_wrapper) β Modular design
ββββββββββββββββββββΌβββββββββββββββββββ€
β β’ GET/PUT/INFO β β’ Server/SharedPVβ
β β’ Async ops β β’ StaticSource β
β β’ Monitoring β β’ NTScalar types β
β β’ RPC client β β’ Metadata β
ββββββββββββββββββββ΄βββββββββββββββββββ
β β
βββββββββββββββββββββββββββββββββββββββ
β PVXS C++ Library (v1.4.1+) β β EPICS PVXS
β - pvxs::client::Context β C++17 based
β - pvxs::server::Server β
β - pvxs::Value, pvxs::SharedPV β
β - pvxs::nt::NTScalar β
βββββββββββββββββββββββββββββββββββββββ
unsafe blocks, leveraging C++17 featuresWhen accessing fields in a Value, these field names are commonly used:
value - The primary data value (double, int32, string, enum, or array)alarm.severity - Alarm severity (0=NO_ALARM, 1=MINOR, 2=MAJOR, 3=INVALID)alarm.status - Alarm status codealarm.message - Alarm message stringtimeStamp.secondsPastEpoch - Seconds since POSIX epochtimeStamp.nanoseconds - Nanoseconds componentdisplay.limitLow - Display lower limitdisplay.limitHigh - Display upper limitdisplay.description - Human-readable descriptiondisplay.units - Engineering units (e.g., "DegC", "m/s")display.precision - Decimal precision for displaycontrol.limitLow - Control lower limitcontrol.limitHigh - Control upper limitcontrol.minStep - Minimum incrementvalueAlarm.lowAlarmLimit - Low alarm thresholdvalueAlarm.highAlarmLimit - High alarm thresholdError: "EPICS_BASE environment variable not set"
# Windows
$env:EPICS_BASE = "C:\epics\base"
# Linux/macOS
export EPICS_BASE=/path/to/epics/base
Error: "cannot find -lpvxs"
$EPICS_PVXS/lib/$EPICS_HOST_ARCH contains pvxs.lib and pvxs.dll (Windows) or libpvxs.so (Linux) or libpvxs.dylib (macOS)Error: "pvxs/client.h: No such file or directory"
$EPICS_PVXS/include/pvxs/Error: "Failed to create context from environment"
EPICS_PVA_ADDR_LIST if neededError: "GET failed: timeout"
| Platform | Status | Compiler Requirements | Notes |
|---|---|---|---|
| Windows x64 | β Fully Tested | MSVC 2017+ (C++17) | Primary development platform, requires CMake |
| Linux x86_64 | π Supported implicitlty but not tested | GCC 7+ or Clang 5+ (C++17) | Build system tested |
| macOS x86_64 | π Supported implicitlty but not tested | Clang 5+ (C++17) | Build system tested |
| macOS ARM64 | π Should work | Clang (C++17) | Apple Silicon compatibility expected |
epics-pvxs crate (non-sys)Contributions are welcome! Please:
This project is licensed under MPL 2.0 (LICENSE)
This project builds upon:
PVXS - The EPICS PVXS library by Michael Davidsaver and contributors
EPICS Base - The Experimental Physics and Industrial Control System
CXX - Safe C++/Rust interop by David Tolnay