Skip to main content

opc_da_client/
provider.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use std::sync::Arc;
4use std::sync::atomic::AtomicUsize;
5
6#[cfg(feature = "test-support")]
7use mockall::automock;
8
9/// A single tag's read result.
10///
11/// Returned by [`OpcProvider::read_tag_values`].
12///
13/// # Examples
14///
15/// ```
16/// use opc_da_client::TagValue;
17///
18/// let tv = TagValue {
19///     tag_id: "Simulation.Random.1".to_string(),
20///     value: "42.5".to_string(),
21///     quality: "Good".to_string(),
22///     timestamp: "2026-01-01 00:00:00".to_string(),
23/// };
24/// assert_eq!(tv.tag_id, "Simulation.Random.1");
25/// ```
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct TagValue {
28    /// The fully qualified tag identifier (e.g., `"Channel1.Device1.Tag1"`).
29    pub tag_id: String,
30    /// The current value as a display string.
31    pub value: String,
32    /// OPC quality indicator (e.g., `"Good"`, `"Bad"`, or `"Uncertain"`).
33    pub quality: String,
34    /// Timestamp of the last value change, formatted as a local time string.
35    pub timestamp: String,
36}
37
38/// Typed value to write to an OPC DA tag.
39///
40/// # Examples
41///
42/// ```
43/// use opc_da_client::OpcValue;
44///
45/// let v = OpcValue::Float(3.14);
46/// assert_eq!(v, OpcValue::Float(3.14));
47/// ```
48#[derive(Debug, Clone, PartialEq)]
49pub enum OpcValue {
50    /// String value (`VT_BSTR`) — server may coerce to target type.
51    String(String),
52    /// 32-bit integer (`VT_I4`).
53    Int(i32),
54    /// 64-bit float (`VT_R8`).
55    Float(f64),
56    /// Boolean (`VT_BOOL`).
57    Bool(bool),
58}
59
60/// Result of a single write operation.
61///
62/// # Examples
63///
64/// ```
65/// use opc_da_client::WriteResult;
66///
67/// let wr = WriteResult {
68///     tag_id: "Tag1".to_string(),
69///     success: true,
70///     error: None,
71/// };
72/// assert!(wr.success);
73/// ```
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct WriteResult {
76    /// The tag that was written to.
77    pub tag_id: String,
78    /// Whether the write succeeded.
79    pub success: bool,
80    /// Error message if the write failed, `None` on success.
81    pub error: Option<String>,
82}
83
84/// Async trait for OPC DA operations.
85///
86/// This is the stable public API. Backend implementations provide
87/// the actual COM/DCOM interaction.
88#[cfg_attr(feature = "test-support", automock)]
89#[async_trait]
90pub trait OpcProvider: Send + Sync {
91    /// List available OPC DA servers on the given host.
92    ///
93    /// # Errors
94    /// Returns `Err` if COM initialization fails or the server registry
95    /// cannot be enumerated.
96    async fn list_servers(&self, host: &str) -> Result<Vec<String>>;
97
98    /// Browse tags recursively, pushing discoveries to `tags_sink`.
99    ///
100    /// # Errors
101    /// Returns `Err` if the server connection fails, the `ProgID` cannot be
102    /// resolved, or the namespace walk encounters an unrecoverable error.
103    async fn browse_tags(
104        &self,
105        server: &str,
106        max_tags: usize,
107        progress: Arc<AtomicUsize>,
108        tags_sink: Arc<std::sync::Mutex<Vec<String>>>,
109    ) -> Result<Vec<String>>;
110
111    /// Read current values for the given tag IDs.
112    ///
113    /// # Errors
114    /// Returns `Err` if the server connection fails, no items can be added
115    /// to the OPC group, or the synchronous read operation fails.
116    async fn read_tag_values(&self, server: &str, tag_ids: Vec<String>) -> Result<Vec<TagValue>>;
117
118    /// Write a value to a single OPC DA tag.
119    ///
120    /// # Errors
121    /// Returns `Err` if the server connection fails, the tag cannot be added
122    /// to the OPC group, or the synchronous write operation fails.
123    async fn write_tag_value(
124        &self,
125        server: &str,
126        tag_id: &str,
127        value: OpcValue,
128    ) -> Result<WriteResult>;
129}