opc-da-client 0.1.3

Backend-agnostic OPC DA client library for Rust — async, trait-based, with RAII COM guard
Documentation

opc-da-client

Crates.io Docs.rs License: MIT

Backend-agnostic OPC DA client library for Rust — async, trait-based, with RAII COM guard.

Features

  • Async/Await API: Built for modern asynchronous Rust using tokio and async-trait.
  • Trait-Based Abstraction: The OpcProvider trait allows for easy mocking and backend swapping.
  • RAII COM Guard: ComGuard handles COM initialization/teardown automatically — no manual CoUninitialize needed.
  • Read & Write Support: Read tag values and write typed values (Int, Float, Bool, String) to OPC tags.
  • Windows COM/DCOM Support: Native OPC DA backend via windows-rs — no external OPC crates needed.
  • Robust Error Handling: Leverages anyhow for clear error chains and friendly_com_hint() for human-readable HRESULT explanations.
  • Test-Friendly: Built-in MockOpcProvider via the test-support feature.

Installation

Add this to your Cargo.toml:

[dependencies]

opc-da-client = "0.1.3"

Prerequisites

  • Operating System: Windows (COM/DCOM is a Windows-only technology).
  • OPC DA Core Components: Ensure the OPC DA Core Components are installed and registered on your system.
  • DCOM Configuration: If connecting to remote servers, appropriate DCOM permissions must be configured.

Usage Examples

Connecting & Listing Servers

Enumerate available OPC DA servers on a local or remote host.

use opc_da_client::{ComGuard, OpcDaWrapper, OpcProvider};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let _guard = ComGuard::new()?;
    let client = OpcDaWrapper::default();

    let servers = client.list_servers("localhost").await?;
    println!("Available Servers:");
    for server in servers {
        println!("  - {}", server);
    }
    Ok(())
}

Reading Tags

Connect to a specific server and read current values for a set of tags.

use opc_da_client::{ComGuard, OpcDaWrapper, OpcProvider};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let _guard = ComGuard::new()?;
    let client = OpcDaWrapper::default();
    let server_progid = "Matrikon.OPC.Simulation.1";
    let tags = vec![
        "Random.Int4".to_string(),
        "Random.Real8".to_string(),
    ];

    let values = client.read_tag_values(server_progid, tags).await?;

    for v in values {
        println!("Tag: {}, Value: {}, Quality: {}, Time: {}",
            v.tag_id, v.value, v.quality, v.timestamp);
    }
    Ok(())
}

Writing a Value

Write a typed value to a single OPC tag.

use opc_da_client::{ComGuard, OpcDaWrapper, OpcProvider, OpcValue};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let _guard = ComGuard::new()?;
    let client = OpcDaWrapper::default();
    let server = "Matrikon.OPC.Simulation.1";

    let result = client
        .write_tag_value(server, "Bucket Brigade.Int4", OpcValue::Int(42))
        .await?;

    if result.success {
        println!("✓ Write succeeded");
    } else {
        println!("✗ Write failed: {}", result.error.unwrap_or_default());
    }
    Ok(())
}

Browsing the Address Space

Recursively discover available tags on an OPC server.

use opc_da_client::{ComGuard, OpcDaWrapper, OpcProvider};
use std::sync::{Arc, Mutex, atomic::AtomicUsize};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let _guard = ComGuard::new()?;
    let client = OpcDaWrapper::default();
    let server_progid = "Matrikon.OPC.Simulation.1";

    let sink = Arc::new(Mutex::new(Vec::new()));
    let progress = Arc::new(AtomicUsize::new(0));

    client.browse_tags(
        server_progid,
        100, // Max tags to discover
        progress,
        sink.clone()
    ).await?;

    let discovered_tags = sink.lock().unwrap();
    println!("Found {} tags", discovered_tags.len());
    Ok(())
}

Architecture

The library is split into a core trait layer and concrete implementations:

  • OpcProvider: The primary async trait defining OPC operations (list, browse, read, write).
  • OpcDaWrapper: The default implementation using native windows-rs COM calls. Generic over ServerConnector for testability; defaults to ComConnector.
  • ComGuard: RAII guard ensuring CoUninitialize is called exactly once per successful CoInitializeEx.

See architecture.md for in-depth design details and spec.md for behavioral contracts.

License

This project is licensed under the MIT License.