opcda-bridge-gateway 0.4.8

OPC DA gateway (Windows COM server)
use std::sync::Arc;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NamespaceOrganization {
    Unspecified,
    Flat,
    Hierarchical,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrowseSource {
    Unspecified,
    Da3,
    Da2,
    Flat,
    Derived,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrowseNodeKind {
    Branch,
    Item,
    BranchAndItem,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BrowseCapabilities {
    pub organization: NamespaceOrganization,
    pub source: BrowseSource,
    pub supports_browse_sessions: bool,
    pub supports_search: bool,
    pub max_page_size: u32,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BrowseNode {
    pub node_key: String,
    pub display_name: String,
    pub kind: BrowseNodeKind,
    pub item_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BrowsePage {
    pub nodes: Vec<BrowseNode>,
    pub next_page_token: Option<String>,
    pub complete: bool,
    pub organization: NamespaceOrganization,
    pub source: BrowseSource,
    pub warning: Option<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InventoryNodeKind {
    Item,
    BranchAndItem,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InventoryEntry {
    pub display_name: String,
    pub item_id: String,
    pub kind: InventoryNodeKind,
    pub breadcrumbs: Vec<String>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct InventoryProgress {
    pub branches_visited: u64,
    pub entries_seen: u64,
    pub unique_items: u64,
    pub active_time_ms: u64,
    pub paused_time_ms: u64,
    pub items_per_second: f64,
    pub estimated_remaining_ms: Option<u64>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InventoryCompleted {
    pub complete: bool,
    pub cancelled: bool,
    pub truncated: bool,
    pub warning: Option<String>,
    pub organization: NamespaceOrganization,
    pub source: BrowseSource,
}

#[derive(Debug, Clone, PartialEq)]
pub enum InventoryEvent {
    Entry(InventoryEntry),
    Progress(InventoryProgress),
    Completed(InventoryCompleted),
}

#[async_trait::async_trait]
pub trait InventoryStream: Send {
    async fn next(&mut self) -> Option<anyhow::Result<InventoryEvent>>;
}

pub trait InventoryControl: Send + Sync {
    fn pause(&self);
    fn resume(&self);
    fn cancel(&self);
    fn is_cancelled(&self) -> bool {
        false
    }
}

pub struct InventoryHandle {
    pub stream: Box<dyn InventoryStream>,
    pub control: Arc<dyn InventoryControl>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TagValue {
    pub tag_id: String,
    /// The exact semantic value returned by OPC DA.
    ///
    /// For a `VT_BSTR`, quote characters are preserved exactly as returned by
    /// the server; the gateway does not add display framing.
    pub value: String,
    pub quality: String,
    pub timestamp: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WriteResult {
    pub tag_id: String,
    pub success: bool,
    pub error: Option<String>,
}

#[derive(Debug, Clone, PartialEq)]
pub enum OpcValue {
    String(String),
    Int(i32),
    Float(f64),
    Bool(bool),
}

#[async_trait::async_trait]
pub trait OpcClient: Send + Sync + 'static {
    async fn list_servers(&self, host: &str) -> anyhow::Result<Vec<String>>;
    async fn get_capabilities(&self, server: &str) -> anyhow::Result<BrowseCapabilities>;
    async fn open_browse_session(&self, server: &str) -> anyhow::Result<String>;
    async fn browse_page(
        &self,
        session_id: &str,
        parent_node_key: Option<&str>,
        page_token: Option<&str>,
        page_size: u32,
        refresh: bool,
    ) -> anyhow::Result<BrowsePage>;
    async fn close_browse_session(&self, session_id: &str) -> anyhow::Result<()>;
    async fn start_inventory(
        &self,
        server: &str,
        batch_size: u32,
    ) -> anyhow::Result<InventoryHandle>;
    async fn read_tag_values(
        &self,
        server: &str,
        tag_ids: Vec<String>,
    ) -> anyhow::Result<Vec<TagValue>>;
    async fn write_tag_value(
        &self,
        server: &str,
        tag_id: &str,
        value: OpcValue,
    ) -> anyhow::Result<WriteResult>;
}

pub type SharedOpcClient<C> = Arc<C>;

#[cfg(test)]
mod tests {
    use super::*;

    struct DefaultInventoryControl;

    impl InventoryControl for DefaultInventoryControl {
        fn pause(&self) {
            std::hint::black_box(());
        }

        fn resume(&self) {
            std::hint::black_box(());
        }

        fn cancel(&self) {
            std::hint::black_box(());
        }
    }

    #[test]
    fn inventory_control_is_not_cancelled_by_default() {
        let control = DefaultInventoryControl;
        control.pause();
        control.resume();
        control.cancel();
        assert!(!control.is_cancelled());
    }
}