Skip to main content

pvxs_sys/
client.rs

1// Copyright 2026 Tine Zata
2// SPDX-License-Identifier: MPL-2.0
3use cxx::UniquePtr;
4use std::fmt;
5
6use crate::{bridge, Result, Value};
7
8/// Monitor event types that can be returned by pop()
9#[derive(Debug, Clone, PartialEq)]
10pub enum MonitorEvent {
11    /// Connection event (when maskConnected(true) is set)
12    Connected(String),
13    /// Disconnection event (when maskDisconnected(true) is set)
14    Disconnected(String),
15    /// Finished event (when maskDisconnected(true) is set).
16    /// Subscription has completed normally and no more events will ever be received.
17    Finished(String),
18    /// Remote error event from server
19    RemoteError(String),
20    /// Standard client side error. Catchs std::exception for client side failures.
21    ClientError(String),
22}
23
24impl fmt::Display for MonitorEvent {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            MonitorEvent::Connected(msg) => write!(f, "Monitor connected: {}", msg),
28            MonitorEvent::Disconnected(msg) => write!(f, "Monitor disconnected: {}", msg),
29            MonitorEvent::Finished(msg) => write!(f, "Monitor finished: {}", msg),
30            MonitorEvent::RemoteError(msg) => write!(f, "Monitor remote error: {}", msg),
31            MonitorEvent::ClientError(msg) => write!(f, "Monitor client error: {}", msg),
32        }
33    }
34}
35
36impl std::error::Error for MonitorEvent {}
37
38/// A PVXS client context for performing PVAccess operations
39///
40/// The Context is the main entry point for interacting with PVAccess.
41/// It manages network connections and provides methods for GET, PUT,
42/// and other PV operations.
43///
44/// # Thread Safety
45///
46/// Context is Send and Sync, and can be safely shared between threads.
47pub struct Context {
48    inner: UniquePtr<bridge::ContextWrapper>,
49}
50
51impl Context {
52    /// Create a new Context configured from environment variables
53    ///
54    /// Reads configuration from `EPICS_PVA_*` environment variables:
55    /// - `EPICS_PVA_ADDR_LIST`: List of server addresses
56    /// - `EPICS_PVA_AUTO_ADDR_LIST`: Auto-discover servers (default: YES)
57    /// - `EPICS_PVA_BROADCAST_PORT`: UDP broadcast port (default: 5076)
58    ///
59    /// # Errors
60    ///
61    /// Returns an error if the context cannot be created.
62    ///
63    /// # Example
64    ///
65    /// ```no_run
66    /// use pvxs_sys::Context;
67    ///
68    /// let ctx = Context::from_env().expect("Failed to create context");
69    /// ```
70    pub fn from_env() -> Result<Self> {
71        let inner = bridge::create_context_from_env()?;
72        Ok(Self { inner })
73    }
74
75    /// Perform a synchronous GET operation
76    ///
77    /// Retrieves the current value of a process variable.
78    ///
79    /// # Arguments
80    ///
81    /// * `pv_name` - The name of the process variable
82    /// * `timeout` - Maximum time to wait in seconds
83    ///
84    /// # Errors
85    ///
86    /// Returns an error if:
87    /// - The PV doesn't exist
88    /// - The operation times out
89    /// - A network error occurs
90    ///
91    /// # Example
92    ///
93    /// ```no_run
94    /// # use pvxs_sys::Context;
95    /// # let mut ctx = Context::from_env().unwrap();
96    /// let value = ctx.get("my:pv:name", 5.0).expect("GET failed");
97    /// println!("Value: {}", value);
98    /// ```
99    pub fn get(&mut self, pv_name: &str, timeout: f64) -> Result<Value> {
100        let inner = bridge::context_get(self.inner.pin_mut(), pv_name, timeout)?;
101        Ok(Value { inner })
102    }
103
104    /// Perform a synchronous PUT operation with a double value
105    ///
106    /// Sets the "value" field of a process variable to a double.
107    ///
108    /// # Arguments
109    ///
110    /// * `pv_name` - The name of the process variable
111    /// * `value` - The value to write
112    /// * `timeout` - Maximum time to wait in seconds
113    ///
114    /// # Errors
115    ///
116    /// Returns an error if:
117    /// - The PV doesn't exist or is read-only
118    /// - The operation times out
119    /// - The value type doesn't match
120    ///
121    /// # Example
122    ///
123    /// ```no_run
124    /// # use pvxs_sys::Context;
125    /// # let mut ctx = Context::from_env().unwrap();
126    /// ctx.put_double("my:pv:double", 42.0, 5.0).expect("PUT failed");
127    /// ```
128    pub fn put_double(&mut self, pv_name: &str, value: f64, timeout: f64) -> Result<()> {
129        bridge::context_put_double(self.inner.pin_mut(), pv_name, value, timeout)?;
130        Ok(())
131    }
132
133    /// Perform a synchronous PUT operation with an int32 value
134    ///
135    /// Sets the "value" field of a process variable to an int32.
136    ///
137    /// # Arguments
138    ///
139    /// * `pv_name` - The name of the process variable
140    /// * `value` - The value to write
141    /// * `timeout` - Maximum time to wait in seconds
142    /// # Errors
143    ///
144    /// Returns an error if:
145    /// - The PV doesn't exist or is read-only
146    /// - The operation times out
147    /// - The value type doesn't match
148    /// # Example
149    ///
150    /// ```no_run
151    /// # use pvxs_sys::Context;
152    /// # let mut ctx = Context::from_env().unwrap();
153    /// ctx.put_int32("my:pv:int", 42, 5.0).expect("PUT failed");
154    /// ```
155    pub fn put_int32(&mut self, pv_name: &str, value: i32, timeout: f64) -> Result<()> {
156        bridge::context_put_int32(self.inner.pin_mut(), pv_name, value, timeout)?;
157        Ok(())
158    }
159
160    /// Perform a synchronous PUT operation with a string value
161    ///
162    /// Sets the "value" field of a process variable to a string.
163    ///
164    /// # Arguments
165    ///
166    /// * `pv_name` - The name of the process variable
167    /// * `value` - The value to write
168    /// * `timeout` - Maximum time to wait in seconds
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if:
173    /// - The PV doesn't exist or is read-only
174    /// - The operation times out
175    /// - The value type doesn't match
176    ///
177    /// # Example
178    ///
179    /// ```no_run
180    /// # use pvxs_sys::Context;
181    /// # let mut ctx = Context::from_env().unwrap();
182    /// ctx.put_string("my:pv:string", "Hello, EPICS!", 5.0).expect("PUT failed");
183    /// ```
184    pub fn put_string(&mut self, pv_name: &str, value: &str, timeout: f64) -> Result<()> {
185        bridge::context_put_string(self.inner.pin_mut(), pv_name, value.to_string(), timeout)?;
186        Ok(())
187    }
188
189    /// Perform a synchronous PUT operation with an enum value
190    ///
191    /// Sets the "value" field of a process variable to an enum (i16).
192    ///
193    /// # Arguments
194    ///
195    /// * `pv_name` - The name of the process variable
196    /// * `value` - The enum value to write
197    /// * `timeout` - Maximum time to wait in seconds
198    ///
199    /// # Errors
200    ///
201    /// Returns an error if:
202    /// - The PV doesn't exist or is read-only
203    /// - The operation times out
204    /// - The value is not a valid enum choice
205    ///
206    /// # Example
207    ///
208    /// ```no_run
209    /// # use pvxs_sys::Context;
210    /// # let mut ctx = Context::from_env().unwrap();
211    /// ctx.put_enum("my:pv:enum", 2, 5.0).expect("PUT failed");
212    /// ```
213    pub fn put_enum(&mut self, pv_name: &str, value: i16, timeout: f64) -> Result<()> {
214        bridge::context_put_enum(self.inner.pin_mut(), pv_name, value, timeout)?;
215        Ok(())
216    }
217
218    /// Perform a synchronous PUT operation with a double array
219    ///
220    /// Sets the "value" field of a process variable to an array of doubles.
221    ///
222    /// # Arguments
223    ///
224    /// * `pv_name` - The name of the process variable
225    /// * `value` - The array of values to write
226    /// * `timeout` - Maximum time to wait in seconds
227    ///
228    /// # Errors
229    ///
230    /// Returns an error if:
231    /// - The PV doesn't exist or is read-only
232    /// - The operation times out
233    /// - The value type doesn't match
234    ///
235    /// # Example
236    ///
237    /// ```no_run
238    /// # use pvxs_sys::Context;
239    /// # let mut ctx = Context::from_env().unwrap();
240    /// ctx.put_double_array("my:pv:array", vec![1.0, 2.0, 3.0], 5.0).expect("PUT failed");
241    /// ```
242    pub fn put_double_array(&mut self, pv_name: &str, value: Vec<f64>, timeout: f64) -> Result<()> {
243        bridge::context_put_double_array(self.inner.pin_mut(), pv_name, value, timeout)?;
244        Ok(())
245    }
246
247    /// Perform a synchronous PUT operation with an int32 array
248    ///
249    /// Sets the "value" field of a process variable to an array of int32s.
250    ///
251    /// # Arguments
252    ///
253    /// * `pv_name` - The name of the process variable
254    /// * `value` - The array of values to write
255    /// * `timeout` - Maximum time to wait in seconds
256    ///
257    /// # Errors
258    ///
259    /// Returns an error if:
260    /// - The PV doesn't exist or is read-only
261    /// - The operation times out
262    /// - The value type doesn't match
263    ///
264    /// # Example
265    ///
266    /// ```no_run
267    /// # use pvxs_sys::Context;
268    /// # let mut ctx = Context::from_env().unwrap();
269    /// ctx.put_int32_array("my:pv:array", vec![10, 20, 30], 5.0).expect("PUT failed");
270    /// ```
271    pub fn put_int32_array(&mut self, pv_name: &str, value: Vec<i32>, timeout: f64) -> Result<()> {
272        bridge::context_put_int32_array(self.inner.pin_mut(), pv_name, value, timeout)?;
273        Ok(())
274    }
275
276    /// Perform a synchronous PUT operation with a string array
277    ///
278    /// Sets the "value" field of a process variable to an array of strings.
279    ///
280    /// # Arguments
281    ///
282    /// * `pv_name` - The name of the process variable
283    /// * `value` - The array of string values to write
284    /// * `timeout` - Maximum time to wait in seconds
285    ///
286    /// # Errors
287    ///
288    /// Returns an error if:
289    /// - The PV doesn't exist or is read-only
290    /// - The operation times out
291    /// - The value type doesn't match
292    ///
293    /// # Example
294    ///
295    /// ```no_run
296    /// # use pvxs_sys::Context;
297    /// # let mut ctx = Context::from_env().unwrap();
298    /// ctx.put_string_array("my:pv:array", vec!["one".to_string(), "two".to_string()], 5.0).expect("PUT failed");
299    /// ```
300    pub fn put_string_array(
301        &mut self,
302        pv_name: &str,
303        value: Vec<String>,
304        timeout: f64,
305    ) -> Result<()> {
306        bridge::context_put_string_array(self.inner.pin_mut(), pv_name, value, timeout)?;
307        Ok(())
308    }
309
310    /// Get type information about a process variable
311    ///
312    /// Retrieves the structure definition without fetching data.
313    /// Useful for discovering the schema of a PV.
314    ///
315    /// # Arguments
316    ///
317    /// * `pv_name` - The name of the process variable
318    /// * `timeout` - Maximum time to wait in seconds
319    ///
320    /// # Example
321    ///
322    /// ```no_run
323    /// # use pvxs_sys::Context;
324    /// # let mut ctx = Context::from_env().unwrap();
325    /// let info = ctx.info("my:pv:name", 5.0).expect("INFO failed");
326    /// println!("PV structure: {}", info);
327    /// ```
328    pub fn info(&mut self, pv_name: &str, timeout: f64) -> Result<Value> {
329        let inner = bridge::context_info(self.inner.pin_mut(), pv_name, timeout)?;
330        Ok(Value { inner })
331    }
332
333    /// Create an RPC (Remote Procedure Call) builder
334    ///
335    /// Creates a builder for performing RPC operations on EPICS servers.
336    /// RPC allows calling server-side functions with arguments.
337    ///
338    /// # Arguments
339    ///
340    /// * `pv_name` - The name of the RPC service/endpoint
341    ///
342    /// # Example
343    ///
344    /// ```no_run
345    /// # use pvxs_sys::Context;
346    /// # let mut ctx = Context::from_env().unwrap();
347    /// let mut rpc = ctx.rpc("my:service").expect("RPC creation failed");
348    /// rpc.arg_string("command", "start");
349    /// rpc.arg_double("value", 42.0);
350    /// let result = rpc.execute(5.0).expect("RPC execution failed");
351    /// ```
352    pub fn rpc(&mut self, pv_name: &str) -> Result<Rpc> {
353        let inner = bridge::context_rpc_create(self.inner.pin_mut(), pv_name.to_string())?;
354        Ok(Rpc { inner })
355    }
356
357    /// Create a monitor for a process variable
358    ///
359    /// Monitors allow you to subscribe to value changes and receive notifications
360    /// when a PV updates, providing an efficient alternative to polling.
361    ///
362    /// # Arguments
363    ///
364    /// * `pv_name` - Name of the process variable to monitor
365    ///
366    /// # Returns
367    ///
368    /// A `Monitor` instance that can be used to receive value updates.
369    ///
370    /// # Example
371    ///
372    /// ```no_run
373    /// # use pvxs_sys::Context;
374    /// # let mut ctx = Context::from_env().unwrap();
375    /// let mut monitor = ctx.monitor("TEST:PV_Double").expect("Monitor creation failed");
376    ///
377    /// monitor.start();
378    ///
379    /// // Check for updates
380    /// if let Some(value) = monitor.try_get_update().expect("Monitor check failed") {
381    ///     println!("PV updated: {}", value);
382    /// }
383    ///
384    /// monitor.stop();
385    /// ```
386    pub fn monitor(&mut self, pv_name: &str) -> Result<Monitor> {
387        let inner = bridge::context_monitor_create(self.inner.pin_mut(), pv_name.to_string())?;
388        Ok(Monitor { inner })
389    }
390
391    /// Create a MonitorBuilder for advanced monitor configuration
392    ///
393    /// Returns a builder that allows configuring event masks and callbacks before
394    /// creating the monitor subscription.
395    ///
396    /// # Arguments
397    ///
398    /// * `pv_name` - Name of the process variable to monitor
399    ///
400    /// # Returns
401    ///
402    /// A `MonitorBuilder` instance for configuring the monitor.
403    ///
404    /// # Example
405    ///
406    /// ```no_run
407    /// use pvxs_sys::Context;
408    ///
409    /// let mut ctx = Context::from_env().expect("Context creation failed");
410    /// let monitor = ctx.monitor_builder("TEST:PV_Double")?
411    ///     .connect_exception(true)      // Throw connection exceptions
412    ///     .disconnect_exception(true)   // Throw disconnection exceptions
413    ///     .exec()
414    ///     .expect("Monitor creation failed");
415    /// # Ok::<(), pvxs_sys::PvxsError>(())
416    /// ```
417    pub fn monitor_builder(&mut self, pv_name: &str) -> Result<MonitorBuilder> {
418        let inner =
419            bridge::context_monitor_builder_create(self.inner.pin_mut(), pv_name.to_string())?;
420        Ok(MonitorBuilder { inner })
421    }
422}
423
424// Context is safe to send between threads
425unsafe impl Send for Context {}
426unsafe impl Sync for Context {}
427
428/// Async implementation for Context
429#[cfg(feature = "async")]
430impl Context {
431    /// Asynchronously read a process variable value
432    ///
433    /// This method uses PVXS RPC for non-blocking operations.
434    ///
435    /// # Arguments
436    ///
437    /// * `pv_name` - The name of the process variable
438    /// * `timeout` - Maximum time to wait in seconds
439    ///
440    /// # Example
441    ///
442    /// ```no_run
443    /// # use pvxs_sys::Context;
444    /// # async fn example() -> Result<(), pvxs_sys::PvxsError> {
445    /// let mut ctx = Context::from_env()?;
446    /// let value = ctx.get_async("my:pv:name", 5.0).await?;
447    /// let val = value.get_field_double("value")?;
448    /// println!("Value: {}", val);
449    /// # Ok(())
450    /// # }
451    /// ```
452    pub async fn get_async(&mut self, pv_name: &str, timeout: f64) -> Result<Value> {
453        let operation = bridge::context_get_async(self.inner.pin_mut(), pv_name, timeout)?;
454        self.wait_for_operation(operation).await
455    }
456
457    /// Asynchronously write a double value to a process variable
458    ///
459    /// # Arguments
460    ///
461    /// * `pv_name` - The name of the process variable
462    /// * `value` - The value to write
463    /// * `timeout` - Maximum time to wait in seconds
464    ///
465    /// # Example
466    ///
467    /// ```no_run
468    /// # use pvxs_sys::Context;
469    /// # async fn example() -> Result<(), pvxs_sys::PvxsError> {
470    /// let mut ctx = Context::from_env()?;
471    /// ctx.put_double_async("my:pv:name", 42.0, 5.0).await?;
472    /// # Ok(())
473    /// # }
474    /// ```
475    pub async fn put_double_async(
476        &mut self,
477        pv_name: &str,
478        value: f64,
479        timeout: f64,
480    ) -> Result<()> {
481        let operation =
482            bridge::context_put_double_async(self.inner.pin_mut(), pv_name, value, timeout)?;
483        self.wait_for_operation(operation).await?;
484        Ok(())
485    }
486
487    /// Asynchronously get type information about a process variable
488    ///
489    /// # Arguments
490    ///
491    /// * `pv_name` - The name of the process variable
492    /// * `timeout` - Maximum time to wait in seconds
493    ///
494    /// # Example
495    ///
496    /// ```no_run
497    /// # use pvxs_sys::Context;
498    /// # async fn example() -> Result<(), pvxs_sys::PvxsError> {
499    /// let mut ctx = Context::from_env()?;
500    /// let info = ctx.info_async("my:pv:name", 5.0).await?;
501    /// println!("PV structure: {}", info);
502    /// # Ok(())
503    /// # }
504    /// ```
505    pub async fn info_async(&mut self, pv_name: &str, timeout: f64) -> Result<Value> {
506        let operation = bridge::context_info_async(self.inner.pin_mut(), pv_name, timeout)?;
507        self.wait_for_operation(operation).await
508    }
509
510    /// Wait for an operation to complete using Tokio's async runtime
511    async fn wait_for_operation(
512        &self,
513        mut operation: cxx::UniquePtr<bridge::OperationWrapper>,
514    ) -> Result<Value> {
515        use tokio::time::{sleep, Duration};
516
517        loop {
518            if bridge::operation_is_done(&operation) {
519                let result = bridge::operation_get_result(operation.pin_mut())?;
520                return Ok(Value { inner: result });
521            }
522
523            // Yield control to the async runtime
524            sleep(Duration::from_millis(10)).await;
525        }
526    }
527}
528
529/// RPC (Remote Procedure Call) builder for EPICS servers
530///
531/// Provides a fluent interface for building and executing RPC calls.
532/// RPC allows calling server-side functions with typed arguments.
533///
534/// # Example
535///
536/// ```no_run
537/// # use pvxs_sys::Context;
538/// # let mut ctx = Context::from_env().unwrap();
539/// let mut rpc = ctx.rpc("my:service").expect("RPC creation failed");
540///
541/// // Add arguments of different types
542/// rpc.arg_string("command", "initialize");
543/// rpc.arg_double("threshold", 3.14);
544/// rpc.arg_int32("count", 100);
545/// rpc.arg_bool("enabled", true);
546///
547/// // Execute synchronously
548/// let result = rpc.execute(5.0).expect("RPC execution failed");
549/// println!("RPC result: {}", result);
550/// ```
551
552/// Monitor represents a subscription to value changes for a process variable.
553///
554/// Monitors allow you to receive notifications when a PV's value changes,
555/// providing an efficient way to track real-time updates without polling.
556///
557/// # Example
558///
559/// ```no_run
560/// use pvxs_sys::Context;
561///
562/// let mut ctx = Context::from_env()?;
563/// let mut monitor = ctx.monitor("MY:PV")?;
564///
565/// monitor.start();
566///
567/// // Wait for updates
568/// loop {
569///     if let Some(value) = monitor.try_get_update()? {
570///         println!("PV updated: {}", value);
571///     }
572///     std::thread::sleep(std::time::Duration::from_millis(100));
573/// }
574/// # Ok::<(), pvxs_sys::PvxsError>(())
575/// ```
576pub struct Monitor {
577    inner: UniquePtr<bridge::MonitorWrapper>,
578}
579
580impl Monitor {
581    /// Start monitoring for value changes
582    ///
583    /// This begins the subscription and the monitor will start receiving updates.
584    ///
585    /// # Example
586    ///
587    /// ```no_run
588    /// # use pvxs_sys::Context;
589    /// # let mut ctx = Context::from_env().unwrap();
590    /// # let mut monitor = ctx.monitor("MY:PV").unwrap();
591    /// monitor.start();
592    /// ```
593    pub fn start(&mut self) -> Result<()> {
594        bridge::monitor_start(self.inner.pin_mut())?;
595        Ok(())
596    }
597
598    /// Stop monitoring for value changes
599    ///
600    /// This ends the subscription and no more updates will be received.
601    ///
602    /// # Example
603    ///
604    /// ```no_run
605    /// # use pvxs_sys::Context;
606    /// # let mut ctx = Context::from_env().unwrap();
607    /// # let mut monitor = ctx.monitor("MY:PV").unwrap();
608    /// # monitor.start();
609    /// monitor.stop()?;
610    /// # Ok::<(), pvxs_sys::PvxsError>(())
611    /// ```
612    pub fn stop(&mut self) -> Result<()> {
613        bridge::monitor_stop(self.inner.pin_mut())?;
614        Ok(())
615    }
616
617    /// Check if the monitor is currently running
618    ///
619    /// # Returns
620    ///
621    /// `true` if the monitor is active and receiving updates, `false` otherwise.
622    ///
623    /// # Example
624    ///
625    /// ```no_run
626    /// # use pvxs_sys::Context;
627    /// # let mut ctx = Context::from_env().unwrap();
628    /// # let mut monitor = ctx.monitor("MY:PV").unwrap();
629    /// monitor.start();
630    /// assert!(monitor.is_running());
631    /// ```
632    pub fn is_running(&self) -> bool {
633        bridge::monitor_is_running(&self.inner)
634    }
635
636    /// Check if there are updates available without blocking
637    ///
638    /// # Returns
639    ///
640    /// `true` if updates are available, `false` otherwise.
641    ///
642    /// # Example
643    ///
644    /// ```no_run
645    /// # use pvxs_sys::Context;
646    /// # let mut ctx = Context::from_env().unwrap();
647    /// # let mut monitor = ctx.monitor("MY:PV").unwrap();
648    /// # monitor.start();
649    /// if monitor.has_update() {
650    ///     let value = monitor.try_get_update()?;
651    ///     println!("Update available: {:?}", value);
652    /// }
653    /// # Ok::<(), pvxs_sys::PvxsError>(())
654    /// ```
655    pub fn has_update(&self) -> bool {
656        bridge::monitor_has_update(&self.inner)
657    }
658
659    /// Get the next update, blocking with a timeout
660    ///
661    /// This method will wait for an update to arrive, up to the specified timeout.
662    ///
663    /// # Arguments
664    ///
665    /// * `timeout` - Maximum time to wait in seconds
666    ///
667    /// # Returns
668    ///
669    /// A `Value` if an update was received within the timeout, or an error.
670    ///
671    /// # Example
672    ///
673    /// ```no_run
674    /// # use pvxs_sys::Context;
675    /// # let mut ctx = Context::from_env().unwrap();
676    /// # let mut monitor = ctx.monitor("MY:PV").unwrap();
677    /// # monitor.start();
678    /// match monitor.get_update(5.0) {
679    ///     Ok(value) => println!("Update received: {}", value),
680    ///     Err(e) => println!("No update within 5 seconds: {}", e),
681    /// }
682    /// # Ok::<(), pvxs_sys::PvxsError>(())
683    /// ```
684    pub fn get_update(&mut self, timeout: f64) -> Result<Value> {
685        let value_wrapper = bridge::monitor_get_update(self.inner.pin_mut(), timeout)?;
686        Ok(Value {
687            inner: value_wrapper,
688        })
689    }
690
691    /// Try to get the next update without blocking
692    ///
693    /// This method returns immediately, either with an update if one is available,
694    /// or `None` if no update is ready.
695    ///
696    /// # Returns
697    ///
698    /// `Some(Value)` if an update is available, `None` otherwise.
699    ///
700    /// # Example
701    ///
702    /// ```no_run
703    /// # use pvxs_sys::Context;
704    /// # let mut ctx = Context::from_env().unwrap();
705    /// # let mut monitor = ctx.monitor("MY:PV").unwrap();
706    /// # monitor.start();
707    /// if let Some(value) = monitor.try_get_update()? {
708    ///     println!("Update: {}", value);
709    /// } else {
710    ///     println!("No update available");
711    /// }
712    /// # Ok::<(), pvxs_sys::PvxsError>(())
713    /// ```
714    pub fn try_get_update(&mut self) -> Result<Option<Value>> {
715        match bridge::monitor_try_get_update(self.inner.pin_mut()) {
716            Ok(value_wrapper) => {
717                if value_wrapper.is_null() {
718                    Ok(None)
719                } else {
720                    Ok(Some(Value {
721                        inner: value_wrapper,
722                    }))
723                }
724            }
725            Err(_) => Ok(None), // No update available or error
726        }
727    }
728
729    /// Pop the next update from the subscription queue (PVXS-style)
730    ///
731    /// This follows the PVXS pattern where `pop()` returns a Value if available,
732    /// or returns Err with MonitorEvent for connection/disconnection events.
733    ///
734    /// # Returns
735    ///
736    /// - `Ok(Some(Value))` if an update is available
737    /// - `Ok(None)` if the queue is empty
738    /// - `Err(MonitorEvent::Connected)` if connection exception (when connect_exception(true), i.e. maskConnected(false))
739    /// - `Err(MonitorEvent::Disconnected)` if disconnection exception (when disconnect_exception(true), i.e. maskDisconnected(false))
740    /// - `Err(MonitorEvent::Finished)` if finished exception (when disconnect_exception(true), i.e. maskDisconnected(false))
741    ///
742    /// Note: The mask configuration controls whether exceptions are suppressed or thrown:
743    /// - connect_exception(true) -> maskConnected(false) -> exceptions are thrown as MonitorEvent::Connected
744    /// - connect_exception(false) -> maskConnected(true) -> exceptions are suppressed/masked out
745    ///
746    /// # Example
747    ///
748    /// ```no_run
749    /// # use pvxs_sys::{Context, MonitorEvent};
750    /// # let mut ctx = Context::from_env().unwrap();
751    /// # let mut monitor = ctx.monitor("MY:PV").unwrap();
752    /// # monitor.start();
753    /// loop {
754    ///     match monitor.pop() {
755    ///         Ok(Some(value)) => println!("Update: {}", value),
756    ///         Ok(None) => break, // Queue empty
757    ///         Err(e) if e.to_string().contains("connected") => {
758    ///             println!("Connection event");
759    ///             break;
760    ///         }
761    ///         Err(e) => {
762    ///             println!("Other error: {}", e);
763    ///             break;
764    ///         }
765    ///     }
766    /// }
767    /// ```
768    pub fn pop(&mut self) -> std::result::Result<Option<Value>, MonitorEvent> {
769        match bridge::monitor_pop(self.inner.pin_mut()) {
770            Ok(value_wrapper) => {
771                if value_wrapper.is_null() {
772                    Ok(None)
773                } else {
774                    Ok(Some(Value {
775                        inner: value_wrapper,
776                    }))
777                }
778            }
779            Err(e) => {
780                let err_msg = e.what();
781                // Check if this is one of our monitor event exceptions
782                if err_msg.contains("Monitor connected:") {
783                    Err(MonitorEvent::Connected(err_msg.to_string()))
784                } else if err_msg.contains("Monitor disconnected:") {
785                    Err(MonitorEvent::Disconnected(err_msg.to_string()))
786                } else if err_msg.contains("Monitor finished:") {
787                    Err(MonitorEvent::Finished(err_msg.to_string()))
788                } else if err_msg.contains("Monitor remote error:") {
789                    Err(MonitorEvent::RemoteError(err_msg.to_string()))
790                } else if err_msg.contains("Monitor client error:") {
791                    Err(MonitorEvent::ClientError(err_msg.to_string()))
792                } else {
793                    // For other errors, panic or convert to a ClientError
794                    Err(MonitorEvent::ClientError(err_msg.to_string()))
795                }
796            }
797        }
798    }
799
800    /// Check if the monitor is connected to the PV
801    ///
802    /// # Returns
803    ///
804    /// `true` if connected to the PV, `false` otherwise.
805    ///
806    /// # Example
807    ///
808    /// ```no_run
809    /// # use pvxs_sys::Context;
810    /// # let mut ctx = Context::from_env().unwrap();
811    /// # let mut monitor = ctx.monitor("MY:PV").unwrap();
812    /// # monitor.start();
813    /// if monitor.is_connected() {
814    ///     println!("Connected to PV");
815    /// } else {
816    ///     println!("Not connected");
817    /// }
818    /// ```
819    pub fn is_connected(&self) -> bool {
820        bridge::monitor_is_connected(&self.inner)
821    }
822
823    /// Get the name of the PV being monitored
824    ///
825    /// # Returns
826    ///
827    /// The PV name as a string.
828    ///
829    /// # Example
830    ///
831    /// ```no_run
832    /// # use pvxs_sys::Context;
833    /// # let mut ctx = Context::from_env().unwrap();
834    /// # let monitor = ctx.monitor("MY:PV").unwrap();
835    /// println!("Monitoring PV: {}", monitor.name());
836    /// ```
837    pub fn name(&self) -> String {
838        bridge::monitor_get_name(&self.inner)
839    }
840}
841
842/// MonitorBuilder provides a builder pattern for creating monitors with advanced configuration
843///
844/// This follows the PVXS MonitorBuilder pattern, allowing configuration of event masks
845/// and callbacks before creating the subscription.
846///
847/// # Example
848///
849/// ```no_run
850/// use pvxs_sys::Context;
851///
852/// let mut ctx = Context::from_env()?;
853/// let monitor = ctx.monitor_builder("MY:PV")?
854///     .connect_exception(true)
855///     .disconnect_exception(true)
856///     .exec()?;
857/// # Ok::<(), pvxs_sys::PvxsError>(())
858/// ```
859pub struct MonitorBuilder {
860    inner: UniquePtr<bridge::MonitorBuilderWrapper>,
861}
862
863impl MonitorBuilder {
864    /// Enable or disable connection exceptions in the monitor queue
865    ///
866    /// This is the user-friendly API - think in terms of what you want to enable.
867    ///
868    /// # Arguments
869    ///
870    /// * `enable` - true to throw connection exceptions, false to suppress them (default: false)
871    ///
872    /// # Example
873    ///
874    /// ```no_run
875    /// # use pvxs_sys::Context;
876    /// # let mut ctx = Context::from_env().unwrap();
877    /// let monitor = ctx.monitor_builder("MY:PV")?
878    ///     .connect_exception(true) // Throw connection exceptions
879    ///     .exec()?;
880    /// # Ok::<(), pvxs_sys::PvxsError>(())
881    /// ```
882    pub fn connect_exception(mut self, enable: bool) -> Self {
883        // PVXS maskConnected(false) = don't mask = throw events, maskConnected(true) = mask = suppress events
884        // So enable=true means mask=false (don't suppress), enable=false means mask=true (suppress)
885        let _ = bridge::monitor_builder_mask_connected(self.inner.pin_mut(), !enable);
886        self
887    }
888
889    /// Enable or disable disconnection exceptions in the monitor queue
890    ///
891    /// This is the user-friendly API - think in terms of what you want to enable.
892    ///
893    /// # Arguments
894    ///
895    /// * `enable` - true to throw disconnection exceptions, false to suppress them (default: true)
896    ///
897    /// # Example
898    ///
899    /// ```no_run
900    /// # use pvxs_sys::Context;
901    /// # let mut ctx = Context::from_env().unwrap();
902    /// let monitor = ctx.monitor_builder("MY:PV")?
903    ///     .disconnect_exception(true) // Throw disconnection exceptions
904    ///     .exec()?;
905    /// # Ok::<(), pvxs_sys::PvxsError>(())
906    /// ```
907    pub fn disconnect_exception(mut self, enable: bool) -> Self {
908        // PVXS maskDisconnected(false) = don't mask = throw events, maskDisconnected(true) = mask = suppress events
909        // So enable=true means mask=false (don't suppress), enable=false means mask=true (suppress)
910        let _ = bridge::monitor_builder_mask_disconnected(self.inner.pin_mut(), !enable);
911        self
912    }
913
914    /// Set an event callback function that will be invoked when the subscription queue becomes not-empty
915    ///
916    /// This follows the PVXS pattern where the callback is invoked when events are available,
917    /// not for each individual event. The callback should then use `pop()` to retrieve events.
918    ///
919    /// # Arguments
920    ///
921    /// * `callback` - Function to be called when events are available
922    ///
923    /// # Example
924    ///
925    /// ```no_run
926    /// # use pvxs_sys::Context;
927    /// # let mut ctx = Context::from_env().unwrap();
928    ///
929    /// extern "C" fn my_callback() {
930    ///     println!("Events available in subscription queue!");
931    /// }
932    ///
933    /// let monitor = ctx.monitor_builder("MY:PV")?
934    ///     .event(my_callback)
935    ///     .exec()?;
936    /// # Ok::<(), pvxs_sys::PvxsError>(())
937    /// ```
938    pub fn event(mut self, callback: extern "C" fn()) -> Self {
939        // Convert function pointer to usize for C++
940        let callback_ptr = callback as usize;
941
942        // Set the callback in C++
943        let _ = bridge::monitor_builder_set_event_callback(self.inner.pin_mut(), callback_ptr);
944        self
945    }
946
947    /// Execute and create the monitor subscription
948    ///
949    /// Creates the actual monitor subscription with the configured settings.
950    ///
951    /// # Returns
952    ///
953    /// A `Monitor` instance ready for use.
954    ///
955    /// # Example
956    ///
957    /// ```no_run
958    /// # use pvxs_sys::Context;
959    /// # let mut ctx = Context::from_env().unwrap();
960    /// let monitor = ctx.monitor_builder("MY:PV")?
961    ///     .connect_exception(true)
962    ///     .exec()?;
963    /// # Ok::<(), pvxs_sys::PvxsError>(())
964    /// ```
965    pub fn exec(mut self) -> Result<Monitor> {
966        let inner = bridge::monitor_builder_exec(self.inner.pin_mut())?;
967        Ok(Monitor { inner })
968    }
969
970    /// Execute with an event callback (for future implementation)
971    ///
972    /// This is a placeholder for future callback support. Currently behaves
973    /// the same as `exec()`.
974    ///
975    /// # Arguments
976    ///
977    /// * `callback_id` - Identifier for the callback (currently unused)
978    ///
979    /// # Example
980    ///
981    /// ```no_run
982    /// # use pvxs_sys::Context;
983    /// # let mut ctx = Context::from_env().unwrap();
984    /// let monitor = ctx.monitor_builder("MY:PV")?
985    ///     .exec_with_callback(123)?;
986    /// # Ok::<(), pvxs_sys::PvxsError>(())
987    /// ```
988    pub fn exec_with_callback(mut self, callback_id: u64) -> Result<Monitor> {
989        let inner = bridge::monitor_builder_exec_with_callback(self.inner.pin_mut(), callback_id)?;
990        Ok(Monitor { inner })
991    }
992}
993
994pub struct Rpc {
995    inner: UniquePtr<bridge::RpcWrapper>,
996}
997
998impl Rpc {
999    /// Add a string argument to the RPC call
1000    ///
1001    /// # Arguments
1002    ///
1003    /// * `name` - The argument name
1004    /// * `value` - The string value
1005    ///
1006    /// # Example
1007    ///
1008    /// ```no_run
1009    /// # use pvxs_sys::Context;
1010    /// # let mut ctx = Context::from_env().unwrap();
1011    /// # let mut rpc = ctx.rpc("my:service").unwrap();
1012    /// rpc.arg_string("filename", "/path/to/file.txt");
1013    /// ```
1014    pub fn arg_string(&mut self, name: &str, value: &str) -> Result<&mut Self> {
1015        bridge::rpc_arg_string(self.inner.pin_mut(), name.to_string(), value.to_string())?;
1016        Ok(self)
1017    }
1018
1019    /// Add a double argument to the RPC call
1020    ///
1021    /// # Arguments
1022    ///
1023    /// * `name` - The argument name
1024    /// * `value` - The double value
1025    pub fn arg_double(&mut self, name: &str, value: f64) -> Result<&mut Self> {
1026        bridge::rpc_arg_double(self.inner.pin_mut(), name.to_string(), value)?;
1027        Ok(self)
1028    }
1029
1030    /// Add an int32 argument to the RPC call
1031    ///
1032    /// # Arguments
1033    ///
1034    /// * `name` - The argument name
1035    /// * `value` - The int32 value
1036    pub fn arg_int32(&mut self, name: &str, value: i32) -> Result<&mut Self> {
1037        bridge::rpc_arg_int32(self.inner.pin_mut(), name.to_string(), value)?;
1038        Ok(self)
1039    }
1040
1041    /// Add a boolean argument to the RPC call
1042    ///
1043    /// # Arguments
1044    ///
1045    /// * `name` - The argument name
1046    /// * `value` - The boolean value
1047    pub fn arg_bool(&mut self, name: &str, value: bool) -> Result<&mut Self> {
1048        bridge::rpc_arg_bool(self.inner.pin_mut(), name.to_string(), value)?;
1049        Ok(self)
1050    }
1051
1052    /// Execute the RPC call synchronously
1053    ///
1054    /// # Arguments
1055    ///
1056    /// * `timeout` - Maximum time to wait in seconds
1057    ///
1058    /// # Returns
1059    ///
1060    /// Returns the result value from the server, or an error if the
1061    /// operation failed or timed out.
1062    ///
1063    /// # Example
1064    ///
1065    /// ```no_run
1066    /// # use pvxs_sys::Context;
1067    /// # let mut ctx = Context::from_env().unwrap();
1068    /// let mut rpc = ctx.rpc("calculator:add").unwrap();
1069    /// rpc.arg_double("a", 10.0);
1070    /// rpc.arg_double("b", 5.0);
1071    /// let result = rpc.execute(5.0).unwrap();
1072    /// let sum = result.get_field_double("result").unwrap();
1073    /// ```
1074    pub fn execute(mut self, timeout: f64) -> Result<Value> {
1075        let inner = bridge::rpc_execute_sync(self.inner.pin_mut(), timeout)?;
1076        Ok(Value { inner })
1077    }
1078}
1079
1080/// Async implementation for RPC
1081#[cfg(feature = "async")]
1082impl Rpc {
1083    /// Execute the RPC call asynchronously
1084    ///
1085    /// # Arguments
1086    ///
1087    /// * `timeout` - Maximum time to wait in seconds
1088    ///
1089    /// # Example
1090    ///
1091    /// ```no_run
1092    /// # use pvxs_sys::Context;
1093    /// # async fn example() -> Result<(), pvxs_sys::PvxsError> {
1094    /// let mut ctx = Context::from_env()?;
1095    /// let mut rpc = ctx.rpc("my:service")?;
1096    /// rpc.arg_string("command", "process");
1097    /// let result = rpc.execute_async(5.0).await?;
1098    /// println!("Async RPC result: {}", result);
1099    /// # Ok(())
1100    /// # }
1101    /// ```
1102    pub async fn execute_async(mut self, timeout: f64) -> Result<Value> {
1103        use tokio::time::{sleep, Duration};
1104
1105        let mut operation = bridge::rpc_execute_async(self.inner.pin_mut(), timeout)?;
1106
1107        loop {
1108            if bridge::operation_is_done(&operation) {
1109                let result = bridge::operation_get_result(operation.pin_mut())?;
1110                return Ok(Value { inner: result });
1111            }
1112
1113            // Yield control to the async runtime
1114            sleep(Duration::from_millis(10)).await;
1115        }
1116    }
1117}