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,
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());
}
}