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}