pvxs-sys
Safe Rust bindings for the EPICS PVXS (PVAccess) library.
This crate provides idiomatic Rust wrappers around the PVXS C++ library using the cxx crate. PVXS implements the PVAccess network protocol used in EPICS (Experimental Physics and Industrial Control System).
Features
Client
- GET — Read scalar and array PV values
- PUT — Write double, int32, string, and enum scalars and arrays
- Monitor — Real-time PV change subscriptions via
MonitorBuilder+pop()
Server
- Start — Network-enabled (
start_from_env) or isolated for testing (start_isolated) - PV creation —
create_pv_double/int32/string/enumand_arrayvariants - POST — Publish new values with automatic alarm computation
- Fetch — Read server-side values with alarm state
- Stop —
stop_drop()consumes the server and frees all resources - Handle —
ServerHandlefor thread-safe access from multiple threads
Each server instance is backed by a dedicated worker thread and a thread-safe
crossbeam channel. All create_pv_*,
post_*, and fetch_* calls are dispatched through this channel, so the
server can be driven safely from any number of threads simultaneously. The
worker thread also applies automatic alarm computation and control-limit
validation on every post_* call — bringing IOC-level alarm behaviour
(value alarms, control limit enforcement, severity/status propagation) into
pure Rust without any external IOC.
Metadata & Alarms
NTScalarMetadataBuilder/NTEnumMetadataBuilder— configure PV metadata at creationControlMetadata,AlarmMetadata— control limits and value alarm thresholdsAlarmSeverity,AlarmStatus— alarm state in fetched values and monitors
Other
set_logger_level— programmatically configure PVXS log levels- Thread-safe
Context(implementsSend + Sync)
Prerequisites
Before using this crate, you need:
- EPICS Base (>= 7.0.9) — epics-base
- PVXS Library (>= 1.4.1) — pvxs
- C++17 Compiler — GCC >= 7, Clang >= 5, or MSVC >= 2017
- CMake (>= 3.10) — Required for building the libevent dependency
Environment Variables
| Variable | Required | Description |
|---|---|---|
EPICS_BASE |
Yes | Path to EPICS base installation |
EPICS_HOST_ARCH |
No | Host architecture (auto-detected if unset) |
EPICS_PVXS |
Yes | Path to PVXS installation (also accepts PVXS_DIR or PVXS_BASE) |
EPICS_PVXS_LIBEVENT |
No | Path to libevent (defaults to bundled libevent within PVXS) |
# Windows (PowerShell)
$env:EPICS_BASE = "C:\epics\base"
$env:EPICS_HOST_ARCH = "windows-x64"
$env:EPICS_PVXS = "C:\epics\pvxs"
# Linux / macOS
Runtime Requirements (Windows)
The build script automatically copies the following DLLs 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
Installation
Add to your Cargo.toml:
[]
= "0.1.1"
Quick Start
Reading a PV (GET)
use ;
Writing PV Values (PUT)
use ;
Monitoring PV Changes
Use monitor_builder together with pop() for real-time subscriptions:
use ;
Creating an EPICS Server
use ;
API Reference
Client — Context
let mut ctx = from_env?;
// GET
let value = ctx.get?;
// PUT (scalars)
ctx.put_double?;
ctx.put_int32?;
ctx.put_string?;
ctx.put_enum?;
// PUT (arrays)
ctx.put_double_array?;
ctx.put_int32_array?;
ctx.put_string_array?;
Monitor
// MonitorBuilder — recommended
let mut monitor = ctx.monitor_builder?
.connect_exception // true = raise MonitorEvent::Connected
.disconnect_exception // true = raise MonitorEvent::Disconnected
.event // optional C callback: extern "C" fn()
.exec?;
monitor.start?;
// pop() — non-blocking, call in a loop
match monitor.pop
// Convenience queries
let connected = monitor.is_connected;
let pending = monitor.has_update;
let name = monitor.name;
monitor.stop?;
Server — Server / ServerHandle
// Start
let server = start_from_env?; // network-enabled
let server = start_isolated?; // test isolation
// Ports
server.tcp_port;
server.udp_port;
// Create PVs
server.create_pv_double?;
server.create_pv_int32?;
server.create_pv_string?;
server.create_pv_enum?;
server.create_pv_double_array?;
server.create_pv_int32_array?;
server.create_pv_string_array?;
// Post values (with automatic alarm computation)
server.post_double?;
server.post_int32?;
server.post_string?;
server.post_enum?;
server.post_double_array?;
server.post_int32_array?;
server.post_string_array?;
// Fetch current value (server-side, with alarm info)
let f = server.fetch_double?; // FetchedDouble { value, alarm_severity, alarm_status, alarm_message, .. }
let f = server.fetch_int32?;
let f = server.fetch_string?;
let f = server.fetch_enum?;
let f = server.fetch_double_array?;
let f = server.fetch_int32_array?;
let f = server.fetch_string_array?;
// Remove a PV
server.remove_pv?;
// Cloneable handle for multi-threaded access
let handle = server.handle; // ServerHandle: Clone + Send
// Stop — consumes server, frees all resources
server.stop_drop?;
Metadata Builders
let metadata = new
.control
.alarm_metadata;
// Enum metadata (no control/alarm fields)
let enum_meta = new;
Value — reading fields
// Scalars
let d = value.get_field_double?;
let i = value.get_field_int32?;
let s = value.get_field_string?;
// Arrays
let da = value.get_field_double_array?;
let ia = value.get_field_int32_array?;
let sa = value.get_field_string_array?;
// Alarm fields
let sev = value.get_field_int32?;
let sta = value.get_field_int32?;
let msg = value.get_field_string?;
// Display value structure
println!;
Logging
// Suppress noisy TCP disconnect messages
set_logger_level.ok;
// Available levels: CRIT < ERR < WARN < INFO < DEBUG
// Wildcard loggers: "pvxs.*"
Building
# Windows
$env:EPICS_BASE = "C:\epics\base"
$env:EPICS_HOST_ARCH = "windows-x64"
$env:EPICS_PVXS = "C:\epics\pvxs"
cargo build
cargo test
# Linux / macOS
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
├── include/
│ └── wrapper.h # Shared C++ wrapper header
├── src/
│ ├── lib.rs # Public Rust API
│ ├── bridge.rs # CXX bridge definitions
│ ├── client.rs # Context, Monitor, MonitorBuilder, Rpc
│ ├── server.rs # Server, ServerHandle, SharedPV, metadata builders
│ ├── value.rs # Value wrapper
│ ├── alarms.rs # AlarmSeverity, AlarmStatus, AlarmConfig
│ ├── metadata.rs # DisplayMetadata, ControlMetadata, AlarmMetadata
│ ├── client_wrapper.cpp # C++ GET/PUT
│ ├── client_wrapper_monitor.cpp # C++ monitor/subscription
│ ├── client_wrapper_rpc.cpp # C++ RPC
│ ├── client_wrapper_async.cpp # C++ async operations
│ └── server_wrapper.cpp # C++ server, SharedPV, NTScalar
├── examples/
│ ├── logging_example.rs
│ └── metadata_server.rs
└── tests/ # Integration tests (isolated, no external IOC needed)
Troubleshooting
EPICS_BASE environment variable not set
$env:EPICS_BASE = "C:\epics\base"
cannot find -lpvxs
Ensure PVXS is built and $EPICS_PVXS/lib/$EPICS_HOST_ARCH contains the PVXS library.
pvxs/client.h: No such file or directory
Ensure PVXS headers are in $EPICS_PVXS/include/pvxs/.
Failed to create context from environment / GET timeout
- Check
EPICS_PVA_ADDR_LISTif targeting a remote IOC - Ensure UDP port 5076 is not blocked by a firewall
Platform Support
| Platform | Status | Notes |
|---|---|---|
| Windows x64 | ✅ Tested | Primary development platform |
| Linux x86_64 | 🔄 Should work | Build system compatible |
| macOS x86_64/ARM64 | 🔄 Should work | Build system compatible |
License
MPL 2.0 — see LICENSE