pvxs-sys
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.
Features
Client Features
- β
Safe FFI Bindings - Memory-safe wrappers using the
cxxcrate - β GET Operations - Read PV values (scalars and arrays)
- β PUT Operations - Write PV values (double, int32, string, enum, and arrays)
- β INFO Operations - Query PV type information and structure
- β Async Support - Async/await support using Tokio (optional feature)
- β Monitor/Subscription - Real-time PV monitoring with customizable callbacks
- β Array Support - Full support for double[], int32[], and string[] arrays
- β RPC Support - Remote procedure calls (client and server)
Server Features
- β Complete Server API - Full PVXS server implementation with network discovery
- β Rich Metadata - NTScalar metadata including display limits, control ranges, and alarms
- β Multiple Data Types - double, int32, string, enum, and array variants
- β SharedPV - Process variables with mailbox (read/write) and readonly modes
- β StaticSource - Organize PVs into logical device groups and hierarchies
- β Network Discovery - Full EPICS beacon and search response functionality
- β Thread-safe - Safe concurrent access to PVs from multiple threads
Crate Structure
This 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.
Architecture
This crate provides a complete EPICS PVXS implementation with separate client and server capabilities:
Client Architecture
- Safe FFI Wrappers - Memory-safe C++ bindings using
cxxcrate - Context Management - Thread-safe client contexts with connection pooling
- Operation Types - Synchronous and asynchronous GET/PUT/INFO operations
- Monitoring - Real-time PV subscription and change notifications
- RPC Support - Remote procedure call client implementation
Server Architecture
- ServerWrapper - Complete PVXS server with network discovery and broadcasting
- SharedPV - Individual process variables with mailbox (read/write) and readonly modes
- StaticSource - Logical grouping of PVs into device/system hierarchies
- Value Management - Proper PVXS value structure handling with
cloneEmpty()for updates - Network Discovery - Full EPICS beacon and search response functionality
Build System
- Modular C++ Sources - Separate
client_wrapper.cppandserver_wrapper.cppimplementations - Shared Header - Common
wrapper.hfor both client and server functionality - FFI Bridge - Complete Rust-C++ type mapping with
cxx-bridge - Cross-platform - Windows (MSVC), Linux (GCC), and macOS (Clang) support
Prerequisites
Before using this crate, you need:
- EPICS Base (>= 7.0.9 recommended) - Download here
- PVXS Library (>= 1.4.1 recommended) - Download here
- C++17 Compiler - GCC >= 7, Clang >= 5, or MSVC >= 2017
- CMake (>= 3.10) - Required for building libevent dependency - Download here
Environment Variables
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)- Examples:
linux-x86_64,windows-x64,darwin-x86
- Examples:
EPICS_PVXS- Path to PVXS installation (required)- Also accepts
PVXS_DIRorPVXS_BASEas alternatives
- Also accepts
EPICS_PVXS_LIBEVENT- Path to libevent installation (optional)- Defaults to bundled libevent within PVXS:
{PVXS}/bundle/usr/{ARCH} - Required DLL:
event_core.dll
- Defaults to bundled libevent within PVXS:
Example setup:
# Linux
# 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"
Installation
Add this to your Cargo.toml:
[]
= "0.1"
# For async support
= { = "0.1", = ["async"] }
Optional Features
async- Enables async/await support using Tokio- Adds
get_async(),put_double_async(), andinfo_async()methods - Requires Tokio runtime
- Example:
cargo run --features async --example async_operations
- Adds
Runtime Requirements (Windows)
For Windows users, the following DLLs are automatically copied by the build script to target/debug and target/release:
pvxs.dllfrom{EPICS_PVXS}\bin\{EPICS_HOST_ARCH}Com.dllfrom{EPICS_BASE}\bin\{EPICS_HOST_ARCH}event_core.dllfrom{EPICS_PVXS}\bundle\usr\{EPICS_HOST_ARCH}\lib
No manual PATH configuration is needed for running examples or tests.
Quick Start
Reading a PV Value (GET)
use ;
Writing PV Values (PUT)
use ;
Monitoring PV Changes
use ;
Creating an EPICS Server with Metadata
use ;
use thread;
use Duration;
Building
Standard Build
# 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
# Build the library
Build Examples
# 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
Available Examples
This repository includes comprehensive examples demonstrating all major features:
Server Examples
metadata_server.rs- Complete EPICS server with rich NTScalar metadata (display, control, alarms)
Run the metadata server example:
# In another terminal, test the PV:
Running Tests
The crate includes an extensive test suite covering all functionality:
# Run all tests
# Run specific test categories
Note: Tests create isolated servers and do not require external IOCs.
Available Examples
This repository includes comprehensive examples demonstrating all major features:
Client Examples
metadata_server.rs- Complete EPICS server with rich NTScalar metadata (display, control, alarms)
Run the metadata server example:
# In another terminal, test the PV:
Running Tests
The crate includes an extensive test suite covering all functionality:
# Run all tests
# Run specific test categories
Note: Tests create isolated servers and do not require external IOCs.
Project Structure
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
API Overview
Client API
// Context - Main client entry point
let mut ctx = from_env?;
// GET operations
let value = ctx.get?;
let v = value.get_field_double?;
// PUT operations (scalars)
ctx.put_double?;
ctx.put_int32?;
ctx.put_string?;
ctx.put_enum?;
// PUT operations (arrays)
ctx.put_double_array?;
ctx.put_int32_array?;
ctx.put_string_array?;
// INFO operations
let info = ctx.info?;
// Monitor operations - Basic usage with get_update()
let mut monitor = ctx.monitor?;
monitor.start?;
let update = monitor.get_update?; // Blocking, waits for data
monitor.stop?;
// Monitor operations - Advanced with MonitorBuilder
let mut monitor = ctx.monitor_builder?
.connect_exception // Throw exception on connection events
.disconnect_exception // Throw exception on disconnection events
.exec?;
monitor.start?;
// Using pop() - Non-blocking, returns immediately
use MonitorEvent;
loop
// Monitor with C-style callback
extern "C"
let mut monitor = ctx.monitor_builder?
.connect_exception // Queue connection events as data
.disconnect_exception // Queue disconnection events as data
.event // Set callback function
.exec?;
monitor.start?;
// Callback is invoked automatically when events occur
Server API
// Create server
let mut server = from_env?; // Network-enabled
let mut server = create_isolated?; // Local-only
// Create PVs with metadata
let metadata = new
.alarm
.display
.control
.value_alarm;
// Scalar PVs
let mut pv1 = server.create_pv_double?;
let mut pv2 = server.create_pv_int32?;
let mut pv3 = server.create_pv_string?;
// Array PVs
let mut pv4 = server.create_pv_double_array?;
let mut pv5 = server.create_pv_int32_array?;
let mut pv6 = server.create_pv_string_array?;
// StaticSource - organize PVs into groups
let mut source = create?;
source.add_pv?;
server.add_source?;
// Server lifecycle
server.start?;
let port = server.tcp_port;
server.stop?;
// Update PV values
pv1.post_double?;
pv2.post_int32?;
pv3.post_string?;
Value API
// Access scalar fields
let d = value.get_field_double?;
let i = value.get_field_int32?;
let s = value.get_field_string?;
let e = value.get_field_enum?;
// Access array fields
let da = value.get_field_double_array?;
let ia = value.get_field_int32_array?;
let sa = value.get_field_string_array?;
// Access alarm information
let severity = value.get_field_int32?;
let status = value.get_field_int32?;
let message = value.get_field_string?;
// Display value structure
println!; // Pretty-print entire structure
Monitor API
The Monitor API provides real-time PV change notifications with flexible event handling:
use ;
// 3. Event-driven with callbacks
extern "C"
// 1. Simple monitoring with get_update() - Blocking
let mut monitor = ctx.monitor?;
monitor.start?;
let value = monitor.get_update?; // Wait up to 5 seconds
monitor.stop?;
// 2. Non-blocking with pop() - Returns immediately
let mut monitor = ctx.monitor_builder?
.connect_exception // Enable connection exceptions
.disconnect_exception // Enable disconnection exceptions
.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
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 (returnsResult<Option<Value>, MonitorEvent>)
MonitorEvent Exceptions:
Connected(String)- Connection established (whenconnect_exception(true))Disconnected(String)- Connection lost (whendisconnect_exception(true))Finished(String)- Monitor closed/finished
Callback Signature:
extern "C"
Architecture
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 β
βββββββββββββββββββββββββββββββββββββββ
Why This Architecture?
- CXX Bridge: Type-safe FFI without manual
unsafeblocks, leveraging C++17 features - Modular C++ Adapters: Separate client modules (wrapper, async, monitor, RPC) and server for maintainability
- Client Layer: Four specialized C++ files handle different client patterns (sync, async, monitoring, RPC)
- Server Layer: Complete server implementation with metadata builders and NTScalar support
- Rust API: Idiomatic Rust interface with builder patterns, error handling, and safe abstractions
Common PV Field Names
When accessing fields in a Value, these field names are commonly used:
NTScalar Structure
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 component
Metadata Fields (when present)
display.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 threshold
Troubleshooting
Build Errors
Error: "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"
- Ensure PVXS is built and installed
- Check that
$EPICS_PVXS/lib/$EPICS_HOST_ARCHcontainspvxs.libandpvxs.dll(Windows) orlibpvxs.so(Linux) orlibpvxs.dylib(macOS)
Error: "pvxs/client.h: No such file or directory"
- Ensure PVXS headers are installed in
$EPICS_PVXS/include/pvxs/
Runtime Errors
Error: "Failed to create context from environment"
- Check that EPICS network configuration is correct
- Verify
EPICS_PVA_ADDR_LISTif needed - Ensure no firewall is blocking UDP port 5076
Error: "GET failed: timeout"
- Increase the timeout value
- Check that the PV exists and IOC is running
- Verify network connectivity to IOC
Platform Support
| 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 |
Implementation Status
β Fully Implemented
- Client Operations: GET, PUT (all types), INFO, async variants
- Server Operations: Full server with SharedPV, StaticSource, NTScalar metadata
- Data Types: double, int32, string, enum, and array variants
- Monitoring: MonitorBuilder with callbacks, event masking, exception handling
- Arrays: Complete support for double[], int32[], string[] in both client and server
- Metadata: NTScalar with display, control, valueAlarm, and enum choices
- Network: Full EPICS discovery, broadcasting, TCP/UDP communication
- Async: Tokio-based async/await for client operations (optional feature)
π§ Planned Enhancements
- RPC (Remote Procedure Call) - Framework exists, needs comprehensive examples
- Custom normative types beyond NTScalar
- Advanced value field navigation utilities
- Connection state callbacks and event handlers
- Batch operations for improved performance
- Higher-level idiomatic
epics-pvxscrate (non-sys)
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Submit a pull request
License
This project is licensed under MPL 2.0 (LICENSE)
References
Acknowledgments
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