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}