# pvxs-sys
Complete low-level FFI bindings for the [EPICS PVXS](https://github.com/epics-base/pvxs) (PVAccess) library.
> **Note**: This is a `-sys` crate providing raw FFI bindings. For a high-level, idiomatic Rust API, use the `epics-pvxs` crate (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 `cxx` crate
- β
**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 `cxx` crate
- **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.cpp` and `server_wrapper.cpp` implementations
- **Shared Header** - Common `wrapper.h` for 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:
1. **EPICS Base** (>= 7.0.9 recommended) - [Download here](https://github.com/epics-base/epics-base)
2. **PVXS Library** (>= 1.4.1 recommended) - [Download here](https://github.com/epics-base/pvxs)
3. **C++17 Compiler** - GCC >= 7, Clang >= 5, or MSVC >= 2017
4. **CMake** (>= 3.10) - Required for building libevent dependency - [Download here](https://cmake.org/download/)
### 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`
- **`EPICS_PVXS`** - Path to PVXS installation (required)
- Also accepts `PVXS_DIR` or `PVXS_BASE` as alternatives
- **`EPICS_PVXS_LIBEVENT`** - Path to libevent installation (optional)
- Defaults to bundled libevent within PVXS: `{PVXS}/bundle/usr/{ARCH}`
- Required DLL: `event_core.dll`
Example setup:
```bash
# 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
```
```powershell
# 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`:
```toml
[dependencies]
pvxs-sys = "0.1"
# For async support
pvxs-sys = { version = "0.1", features = ["async"] }
```
### Optional Features
- **`async`** - Enables async/await support using Tokio
- Adds `get_async()`, `put_double_async()`, and `info_async()` methods
- Requires Tokio runtime
- Example: `cargo run --features async --example async_operations`
### Runtime Requirements (Windows)
For Windows users, the following DLLs are automatically copied by the build script to `target/debug` and `target/release`:
1. **`pvxs.dll`** from `{EPICS_PVXS}\bin\{EPICS_HOST_ARCH}`
2. **`Com.dll`** from `{EPICS_BASE}\bin\{EPICS_HOST_ARCH}`
3. **`event_core.dll`** from `{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)
```rust
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(())
}
```
### Writing PV Values (PUT)
```rust
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(())
}
```
### Monitoring PV Changes
```rust
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(())
}
```
### Creating an EPICS Server with Metadata
```rust
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(())
}
```
## Building
### Standard Build
```powershell
# 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
```
```bash
# 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
```
### Build Examples
```powershell
# 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:
```bash
cargo run --example metadata_server
# In another terminal, test the PV:
pvget temperature:sensor1
pvinfo temperature:sensor1 # See full metadata structure
```
### Running Tests
The crate includes an extensive test suite covering all functionality:
```bash
# 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.
### 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:
```bash
cargo run --example metadata_server
# In another terminal, test the PV:
pvget temperature:sensor1
pvinfo temperature:sensor1 # See full metadata structure
```
### Running Tests
The crate includes an extensive test suite covering all functionality:
```bash
# 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.
## Project Structure
```text
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
```rust
// 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
```
### Server API
```rust
// 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")?;
```
### Value API
```rust
// 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
```
### Monitor API
The Monitor API provides real-time PV change notifications with flexible event handling:
```rust
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/finished
**Callback Signature:**
```rust
extern "C" fn callback() {
// Called from PVXS worker thread
// Keep processing minimal - no blocking operations
}
```
## Architecture
The crate uses a four-layer architecture with modular client/server separation optimized for C++17:
```text
βββββββββββββββββββββββββββββββββββββββ
β 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?
1. **CXX Bridge**: Type-safe FFI without manual `unsafe` blocks, leveraging C++17 features
2. **Modular C++ Adapters**: Separate client modules (wrapper, async, monitor, RPC) and server for maintainability
3. **Client Layer**: Four specialized C++ files handle different client patterns (sync, async, monitoring, RPC)
4. **Server Layer**: Complete server implementation with metadata builders and NTScalar support
5. **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 code
- **`alarm.message`** - Alarm message string
- **`timeStamp.secondsPastEpoch`** - Seconds since POSIX epoch
- **`timeStamp.nanoseconds`** - Nanoseconds component
### Metadata Fields (when present)
- **`display.limitLow`** - Display lower limit
- **`display.limitHigh`** - Display upper limit
- **`display.description`** - Human-readable description
- **`display.units`** - Engineering units (e.g., "DegC", "m/s")
- **`display.precision`** - Decimal precision for display
- **`control.limitLow`** - Control lower limit
- **`control.limitHigh`** - Control upper limit
- **`control.minStep`** - Minimum increment
- **`valueAlarm.lowAlarmLimit`** - Low alarm threshold
- **`valueAlarm.highAlarmLimit`** - High alarm threshold
## Troubleshooting
### Build Errors
**Error: "EPICS_BASE environment variable not set"**
```powershell
# 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_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"**
- 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_LIST` if 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
| 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-pvxs` crate (non-sys)
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes with tests
4. Submit a pull request
## License
This project is licensed under MPL 2.0 ([LICENSE](LICENSE))
## References
- [EPICS Website](https://epics-controls.org/)
- [PVXS Documentation](https://epics-base.github.io/pvxs/)
- [PVXS GitHub Repository](https://github.com/epics-base/pvxs)
- [CXX Crate Documentation](https://cxx.rs/)
## 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