Skip to main content

pvxs_sys/
server.rs

1// Copyright 2026 Tine Zata
2// SPDX-License-Identifier: MPL-2.0
3use crossbeam_channel as channel;
4use cxx::UniquePtr;
5use std::collections::HashMap;
6use std::thread;
7
8use crate::{
9    bridge, compute_alarm_for_scalar, AlarmConfig, AlarmMetadata, AlarmSeverity, AlarmStatus,
10    ControlMetadata, DisplayMetadata, PvxsError, Result, Value,
11};
12
13/// Internal server implementation
14///
15/// Low-level server that manages PVs without automatic alarm handling.
16/// Users should typically use the `Server` type instead, which provides
17/// managed PVs with automatic alarm and validation logic.
18pub(crate) struct ServerImpl {
19    inner: UniquePtr<bridge::ServerWrapper>,
20}
21
22impl ServerImpl {
23    /// Create a server from environment variables
24    ///
25    /// Reads configuration from EPICS environment variables for network setup.
26    ///
27    /// # Errors
28    ///
29    /// Returns an error if the server cannot be created or configured.
30    pub fn from_env() -> Result<Self> {
31        let inner = bridge::server_create_from_env()?;
32        Ok(Self { inner })
33    }
34
35    /// Create an isolated server for testing
36    ///
37    /// Creates a server that operates in isolation, using system-assigned ports
38    /// and avoiding conflicts with other servers. Ideal for unit tests.
39    ///
40    /// # Example
41    ///
42    /// ```no_run
43    /// use pvxs_sys::Server;
44    ///
45    /// let server = Server::start_isolated()?;
46    /// println!("Isolated server started on TCP port {}", server.tcp_port());
47    /// server.stop_drop()?;
48    /// # Ok::<(), pvxs_sys::PvxsError>(())
49    /// ```
50    pub fn create_isolated() -> Result<Self> {
51        let inner = bridge::server_create_isolated()?;
52        Ok(Self { inner })
53    }
54
55    /// Start the server
56    ///
57    /// Begins listening for client connections and serving PVs.
58    ///
59    /// # Errors
60    ///
61    /// Returns an error if the server cannot be started (e.g., port conflicts).
62    pub fn start(&mut self) -> Result<()> {
63        bridge::server_start(self.inner.pin_mut())?;
64        Ok(())
65    }
66
67    /// Stop the server
68    ///
69    /// Stops listening for connections and shuts down the server.
70    ///
71    /// # Errors
72    ///
73    /// Returns an error if the server cannot be stopped cleanly.
74    pub fn stop(&mut self) -> Result<()> {
75        bridge::server_stop(self.inner.pin_mut())?;
76        Ok(())
77    }
78
79    /// Add a PV to the server (internal use only)
80    ///
81    /// Makes a process variable available to clients under the given name.
82    /// This is now internal - use create_pv_* methods instead.
83    ///
84    /// # Arguments
85    ///
86    /// * `name` - The PV name that clients will use
87    /// * `pv` - The SharedPV to add
88    pub(crate) fn add_pv(&mut self, name: &str, pv: &mut SharedPV) -> Result<()> {
89        bridge::server_add_pv(self.inner.pin_mut(), name.to_string(), pv.inner.pin_mut())?;
90        Ok(())
91    }
92
93    /// Remove a PV from the server
94    ///
95    /// Removes the PV with the given name from the server.
96    ///
97    /// # Arguments
98    ///
99    /// * `name` - The name of the PV to remove
100    pub fn remove_pv(&mut self, name: &str) -> Result<()> {
101        bridge::server_remove_pv(self.inner.pin_mut(), name.to_string())?;
102        Ok(())
103    }
104
105    // TODO: TZ: Review later if needed
106    /*
107    /// Add a static source to the server
108    ///
109    /// Static sources provide collections of PVs with a common configuration.
110    ///
111    /// # Arguments
112    ///
113    /// * `name` - Name for this source
114    /// * `source` - The StaticSource to add
115    /// * `order` - Priority order (lower numbers have higher priority)
116    pub fn add_source(&mut self, name: &str, source: &mut StaticSource, order: i32) -> Result<()> {
117        bridge::server_add_source(self.inner.pin_mut(), name.to_string(), source.inner.pin_mut(), order)?;
118        Ok(())
119    }*/
120
121    /// Get the TCP port the server is listening on
122    ///
123    /// Returns 0 if the server is not started.
124    pub fn tcp_port(&self) -> u16 {
125        bridge::server_get_tcp_port(&self.inner)
126    }
127
128    /// Get the UDP port the server is using
129    ///
130    /// Returns 0 if the server is not started.
131    pub fn udp_port(&self) -> u16 {
132        bridge::server_get_udp_port(&self.inner)
133    }
134
135    /// Create and add a new mailbox SharedPV with a double value and metadata
136    ///
137    /// Mailbox PVs allow both reading and writing by clients.
138    /// The PV is automatically added to the server with the given name.
139    ///
140    /// # Arguments
141    ///
142    /// * `name` - The PV name that clients will use
143    /// * `initial_value` - Initial value for the PV
144    /// * `metadata` - Metadata for the scalar PV
145    ///
146    /// # Example
147    ///
148    /// ```no_run
149    /// # use pvxs_sys::{Server, NTScalarMetadataBuilder};
150    /// # let mut server = Server::start_isolated().unwrap();
151    /// let pv = server.create_pv_double("test:double", 42.5, NTScalarMetadataBuilder::new())?;
152    /// # Ok::<(), pvxs_sys::PvxsError>(())
153    /// ```
154    pub fn create_pv_double(
155        &mut self,
156        name: &str,
157        initial_value: f64,
158        metadata: NTScalarMetadataBuilder,
159    ) -> Result<SharedPV> {
160        let mut pv = SharedPV::create_mailbox()?;
161        pv.open_double(initial_value, metadata)?;
162        self.add_pv(name, &mut pv)?;
163        Ok(pv)
164    }
165
166    /// Create and add a new mailbox SharedPV with a double array value and metadata
167    ///
168    /// Create should fail if array is empty.
169    /// The PV is automatically added to the server with the given name.
170    ///
171    /// # Arguments
172    ///
173    /// * `name` - The PV name that clients will use
174    /// * `initial_value` - Initial array value for the PV
175    /// * `metadata` - Metadata for the scalar array PV
176    pub fn create_pv_double_array(
177        &mut self,
178        name: &str,
179        initial_value: Vec<f64>,
180        metadata: NTScalarMetadataBuilder,
181    ) -> Result<SharedPV> {
182        if initial_value.is_empty() {
183            return Err(PvxsError::new("Initial double array cannot be empty"));
184        }
185        let mut pv = SharedPV::create_mailbox()?;
186        pv.open_double_array(initial_value, metadata)?;
187        self.add_pv(name, &mut pv)?;
188        Ok(pv)
189    }
190
191    /// Create and add a new mailbox SharedPV with an int32 value and metadata
192    ///
193    /// The PV is automatically added to the server with the given name.
194    ///
195    /// # Arguments
196    ///
197    /// * `name` - The PV name that clients will use
198    /// * `initial_value` - Initial value for the PV
199    /// * `metadata` - Metadata for the scalar PV
200    pub fn create_pv_int32(
201        &mut self,
202        name: &str,
203        initial_value: i32,
204        metadata: NTScalarMetadataBuilder,
205    ) -> Result<SharedPV> {
206        let mut pv = SharedPV::create_mailbox()?;
207        pv.open_int32(initial_value, metadata)?;
208        self.add_pv(name, &mut pv)?;
209        Ok(pv)
210    }
211
212    /// Create and add a new mailbox SharedPV with an int32 array value and metadata
213    ///
214    /// Create should fail if array is empty.
215    /// The PV is automatically added to the server with the given name.
216    ///
217    /// # Arguments
218    ///
219    /// * `name` - The PV name that clients will use
220    /// * `initial_value` - Initial array value for the PV
221    /// * `metadata` - Metadata for the array PV
222    pub fn create_pv_int32_array(
223        &mut self,
224        name: &str,
225        initial_value: Vec<i32>,
226        metadata: NTScalarMetadataBuilder,
227    ) -> Result<SharedPV> {
228        if initial_value.is_empty() {
229            return Err(PvxsError::new("Initial int32 array cannot be empty"));
230        }
231        let mut pv = SharedPV::create_mailbox()?;
232        pv.open_int32_array(initial_value, metadata)?;
233        self.add_pv(name, &mut pv)?;
234        Ok(pv)
235    }
236
237    /// Create and add a new mailbox SharedPV with a string value and metadata
238    ///
239    /// The PV is automatically added to the server with the given name.
240    ///
241    /// # Arguments
242    ///
243    /// * `name` - The PV name that clients will use
244    /// * `initial_value` - Initial value for the PV
245    /// * `metadata` - Metadata for the string PV
246    pub fn create_pv_string(
247        &mut self,
248        name: &str,
249        initial_value: &str,
250        metadata: NTScalarMetadataBuilder,
251    ) -> Result<SharedPV> {
252        let mut pv = SharedPV::create_mailbox()?;
253        pv.open_string(initial_value, metadata)?;
254        self.add_pv(name, &mut pv)?;
255        Ok(pv)
256    }
257
258    /// Create and add a new mailbox SharedPV with a string array value and metadata
259    ///
260    /// Create should fail if array is empty.
261    /// The PV is automatically added to the server with the given name.
262    ///
263    /// # Arguments
264    ///
265    /// * `name` - The PV name that clients will use
266    /// * `initial_value` - Initial array value for the PV
267    /// * `metadata` - Metadata for the string array PV
268    pub fn create_pv_string_array(
269        &mut self,
270        name: &str,
271        initial_value: Vec<String>,
272        metadata: NTScalarMetadataBuilder,
273    ) -> Result<SharedPV> {
274        if initial_value.is_empty() {
275            return Err(PvxsError::new("Initial string array cannot be empty"));
276        }
277        let mut pv = SharedPV::create_mailbox()?;
278        pv.open_string_array(initial_value, metadata)?;
279        self.add_pv(name, &mut pv)?;
280        Ok(pv)
281    }
282
283    /// Create and add a new mailbox SharedPV with an enum value and metadata
284    ///
285    /// The PV is automatically added to the server with the given name.
286    ///
287    /// # Arguments
288    ///
289    /// * `name` - The PV name that clients will use
290    /// * `choices` - List of string choices for the enum
291    /// * `selected_index` - Initial selected index (0-based)
292    /// * `metadata` - Metadata for the enum PV
293    pub fn create_pv_enum(
294        &mut self,
295        name: &str,
296        choices: Vec<&str>,
297        selected_index: i16,
298        metadata: NTEnumMetadataBuilder,
299    ) -> Result<SharedPV> {
300        let mut pv = SharedPV::create_mailbox()?;
301        pv.open_enum(choices, selected_index, metadata)?;
302        self.add_pv(name, &mut pv)?;
303        Ok(pv)
304    }
305
306    // TODO: TZ - template for readonly PVs if needed in the future
307    /*
308    /// Create and add a new readonly SharedPV with a double value and metadata
309    ///
310    /// Readonly PVs only allow reading by clients.
311    /// The PV is automatically added to the server with the given name.
312    ///
313    /// # Arguments
314    ///
315    /// * `name` - The PV name that clients will use
316    /// * `initial_value` - Initial value for the PV
317    /// * `metadata` - Metadata for the scalar PV
318    pub fn create_readonly_pv_double(&mut self, name: &str, initial_value: f64, metadata: NTScalarMetadataBuilder) -> Result<SharedPV> {
319        let mut pv = SharedPV::create_readonly()?;
320        pv.open_double(initial_value, metadata)?;
321        self.add_pv(name, &mut pv)?;
322        Ok(pv)
323    }*/
324}
325
326/// Fetched double value with alarm information
327#[derive(Debug, Clone)]
328pub struct FetchedDouble {
329    pub value: f64,
330    pub alarm_severity: AlarmSeverity,
331    pub alarm_status: AlarmStatus,
332    pub alarm_message: String,
333    pub display_metadata: Option<DisplayMetadata>,
334    pub control_metadata: Option<ControlMetadata>,
335    pub alarm_metadata: Option<AlarmMetadata>,
336}
337
338/// Fetched int32 value with alarm information
339#[derive(Debug, Clone)]
340pub struct FetchedInt32 {
341    pub value: i32,
342    pub alarm_severity: AlarmSeverity,
343    pub alarm_status: AlarmStatus,
344    pub alarm_message: String,
345    pub display_metadata: Option<DisplayMetadata>,
346    pub control_metadata: Option<ControlMetadata>,
347    pub alarm_metadata: Option<AlarmMetadata>,
348}
349
350/// Fetched string value with alarm information
351#[derive(Debug, Clone)]
352pub struct FetchedString {
353    pub value: String,
354    pub alarm_severity: AlarmSeverity,
355    pub alarm_status: AlarmStatus,
356    pub alarm_message: String,
357}
358
359/// Fetched double array value with alarm information
360#[derive(Debug, Clone)]
361pub struct FetchedDoubleArray {
362    pub value: Vec<f64>,
363    pub alarm_severity: AlarmSeverity,
364    pub alarm_status: AlarmStatus,
365    pub alarm_message: String,
366    pub display_metadata: Option<DisplayMetadata>,
367    pub control_metadata: Option<ControlMetadata>,
368    pub alarm_metadata: Option<AlarmMetadata>,
369}
370
371/// Fetched int32 array value with alarm information
372#[derive(Debug, Clone)]
373pub struct FetchedInt32Array {
374    pub value: Vec<i32>,
375    pub alarm_severity: AlarmSeverity,
376    pub alarm_status: AlarmStatus,
377    pub alarm_message: String,
378    pub display_metadata: Option<DisplayMetadata>,
379    pub control_metadata: Option<ControlMetadata>,
380    pub alarm_metadata: Option<AlarmMetadata>,
381}
382
383/// Fetched string array value with alarm information
384#[derive(Debug, Clone)]
385pub struct FetchedStringArray {
386    pub value: Vec<String>,
387    pub alarm_severity: AlarmSeverity,
388    pub alarm_status: AlarmStatus,
389    pub alarm_message: String,
390}
391
392/// Fetched enum value with alarm information
393#[derive(Debug, Clone)]
394pub struct FetchedEnum {
395    pub value: i16,
396    pub value_choices: Vec<String>,
397    pub alarm_severity: AlarmSeverity,
398    pub alarm_status: AlarmStatus,
399    pub alarm_message: String,
400}
401
402enum ManagerCommand {
403    CreateDouble {
404        name: String,
405        initial: f64,
406        metadata: NTScalarMetadataBuilder,
407        reply: channel::Sender<Result<()>>,
408    },
409    CreateDoubleArray {
410        name: String,
411        initial: Vec<f64>,
412        metadata: NTScalarMetadataBuilder,
413        reply: channel::Sender<Result<()>>,
414    },
415    CreateInt32 {
416        name: String,
417        initial: i32,
418        metadata: NTScalarMetadataBuilder,
419        reply: channel::Sender<Result<()>>,
420    },
421    CreateInt32Array {
422        name: String,
423        initial: Vec<i32>,
424        metadata: NTScalarMetadataBuilder,
425        reply: channel::Sender<Result<()>>,
426    },
427    CreateString {
428        name: String,
429        initial: String,
430        metadata: NTScalarMetadataBuilder,
431        reply: channel::Sender<Result<()>>,
432    },
433    CreateStringArray {
434        name: String,
435        initial: Vec<String>,
436        metadata: NTScalarMetadataBuilder,
437        reply: channel::Sender<Result<()>>,
438    },
439    CreateEnum {
440        name: String,
441        choices: Vec<String>,
442        selected_index: i16,
443        metadata: NTEnumMetadataBuilder,
444        reply: channel::Sender<Result<()>>,
445    },
446    PostDouble {
447        name: String,
448        value: f64,
449        reply: channel::Sender<Result<()>>,
450    },
451    PostDoubleArray {
452        name: String,
453        value: Vec<f64>,
454        reply: channel::Sender<Result<()>>,
455    },
456    PostInt32 {
457        name: String,
458        value: i32,
459        reply: channel::Sender<Result<()>>,
460    },
461    PostInt32Array {
462        name: String,
463        value: Vec<i32>,
464        reply: channel::Sender<Result<()>>,
465    },
466    PostString {
467        name: String,
468        value: String,
469        reply: channel::Sender<Result<()>>,
470    },
471    PostStringArray {
472        name: String,
473        value: Vec<String>,
474        reply: channel::Sender<Result<()>>,
475    },
476    PostEnum {
477        name: String,
478        value: i16,
479        reply: channel::Sender<Result<()>>,
480    },
481    Remove {
482        name: String,
483        reply: channel::Sender<Result<()>>,
484    },
485    FetchDouble {
486        name: String,
487        reply: channel::Sender<Result<FetchedDouble>>,
488    },
489    FetchInt32 {
490        name: String,
491        reply: channel::Sender<Result<FetchedInt32>>,
492    },
493    FetchString {
494        name: String,
495        reply: channel::Sender<Result<FetchedString>>,
496    },
497    FetchDoubleArray {
498        name: String,
499        reply: channel::Sender<Result<FetchedDoubleArray>>,
500    },
501    FetchInt32Array {
502        name: String,
503        reply: channel::Sender<Result<FetchedInt32Array>>,
504    },
505    FetchStringArray {
506        name: String,
507        reply: channel::Sender<Result<FetchedStringArray>>,
508    },
509    FetchEnum {
510        name: String,
511        reply: channel::Sender<Result<FetchedEnum>>,
512    },
513    Stop {
514        reply: channel::Sender<Result<()>>,
515    },
516}
517
518enum ManagedPv {
519    Double {
520        pv: SharedPV,
521        alarm: AlarmConfig,
522        last: f64,
523    },
524    DoubleArray(SharedPV),
525    Int32 {
526        pv: SharedPV,
527        alarm: AlarmConfig,
528        last: i32,
529    },
530    Int32Array(SharedPV),
531    String(SharedPV),
532    StringArray(SharedPV),
533    PvEnum(SharedPV),
534}
535
536/// Handle to a running PVXS server
537///
538/// Provides methods to interact with a running server without blocking.
539/// Can be cloned and shared across threads.
540#[derive(Clone)]
541pub struct ServerHandle {
542    tx: channel::Sender<ManagerCommand>,
543    tcp_port: u16,
544    udp_port: u16,
545}
546
547impl ServerHandle {
548    pub fn tcp_port(&self) -> u16 {
549        self.tcp_port
550    }
551
552    pub fn udp_port(&self) -> u16 {
553        self.udp_port
554    }
555
556    pub fn create_pv_double(
557        &self,
558        name: &str,
559        initial: f64,
560        metadata: NTScalarMetadataBuilder,
561    ) -> Result<()> {
562        let (reply_tx, reply_rx) = channel::bounded(1);
563        self.tx
564            .send(ManagerCommand::CreateDouble {
565                name: name.to_string(),
566                initial,
567                metadata,
568                reply: reply_tx,
569            })
570            .map_err(|_| PvxsError::new("Server worker stopped"))?;
571        reply_rx
572            .recv()
573            .map_err(|_| PvxsError::new("Server worker stopped"))?
574    }
575
576    pub fn create_pv_double_array(
577        &self,
578        name: &str,
579        initial: Vec<f64>,
580        metadata: NTScalarMetadataBuilder,
581    ) -> Result<()> {
582        let (reply_tx, reply_rx) = channel::bounded(1);
583        self.tx
584            .send(ManagerCommand::CreateDoubleArray {
585                name: name.to_string(),
586                initial,
587                metadata,
588                reply: reply_tx,
589            })
590            .map_err(|_| PvxsError::new("Server worker stopped"))?;
591        reply_rx
592            .recv()
593            .map_err(|_| PvxsError::new("Server worker stopped"))?
594    }
595
596    pub fn create_pv_int32(
597        &self,
598        name: &str,
599        initial: i32,
600        metadata: NTScalarMetadataBuilder,
601    ) -> Result<()> {
602        let (reply_tx, reply_rx) = channel::bounded(1);
603        self.tx
604            .send(ManagerCommand::CreateInt32 {
605                name: name.to_string(),
606                initial,
607                metadata,
608                reply: reply_tx,
609            })
610            .map_err(|_| PvxsError::new("Server worker stopped"))?;
611        reply_rx
612            .recv()
613            .map_err(|_| PvxsError::new("Server worker stopped"))?
614    }
615
616    pub fn create_pv_int32_array(
617        &self,
618        name: &str,
619        initial: Vec<i32>,
620        metadata: NTScalarMetadataBuilder,
621    ) -> Result<()> {
622        let (reply_tx, reply_rx) = channel::bounded(1);
623        self.tx
624            .send(ManagerCommand::CreateInt32Array {
625                name: name.to_string(),
626                initial,
627                metadata,
628                reply: reply_tx,
629            })
630            .map_err(|_| PvxsError::new("Server worker stopped"))?;
631        reply_rx
632            .recv()
633            .map_err(|_| PvxsError::new("Server worker stopped"))?
634    }
635
636    pub fn create_pv_string(
637        &self,
638        name: &str,
639        initial: &str,
640        metadata: NTScalarMetadataBuilder,
641    ) -> Result<()> {
642        let (reply_tx, reply_rx) = channel::bounded(1);
643        self.tx
644            .send(ManagerCommand::CreateString {
645                name: name.to_string(),
646                initial: initial.to_string(),
647                metadata,
648                reply: reply_tx,
649            })
650            .map_err(|_| PvxsError::new("Server worker stopped"))?;
651        reply_rx
652            .recv()
653            .map_err(|_| PvxsError::new("Server worker stopped"))?
654    }
655
656    pub fn create_pv_string_array(
657        &self,
658        name: &str,
659        initial: Vec<String>,
660        metadata: NTScalarMetadataBuilder,
661    ) -> Result<()> {
662        let (reply_tx, reply_rx) = channel::bounded(1);
663        self.tx
664            .send(ManagerCommand::CreateStringArray {
665                name: name.to_string(),
666                initial,
667                metadata,
668                reply: reply_tx,
669            })
670            .map_err(|_| PvxsError::new("Server worker stopped"))?;
671        reply_rx
672            .recv()
673            .map_err(|_| PvxsError::new("Server worker stopped"))?
674    }
675
676    pub fn create_pv_enum(
677        &self,
678        name: &str,
679        choices: Vec<&str>,
680        selected_index: i16,
681        metadata: NTEnumMetadataBuilder,
682    ) -> Result<()> {
683        let (reply_tx, reply_rx) = channel::bounded(1);
684        self.tx
685            .send(ManagerCommand::CreateEnum {
686                name: name.to_string(),
687                choices: choices.iter().map(|s| s.to_string()).collect(),
688                selected_index,
689                metadata,
690                reply: reply_tx,
691            })
692            .map_err(|_| PvxsError::new("Server worker stopped"))?;
693        reply_rx
694            .recv()
695            .map_err(|_| PvxsError::new("Server worker stopped"))?
696    }
697
698    pub fn post_double(&self, name: &str, value: f64) -> Result<()> {
699        let (reply_tx, reply_rx) = channel::bounded(1);
700        self.tx
701            .send(ManagerCommand::PostDouble {
702                name: name.to_string(),
703                value,
704                reply: reply_tx,
705            })
706            .map_err(|_| PvxsError::new("Server worker stopped"))?;
707        reply_rx
708            .recv()
709            .map_err(|_| PvxsError::new("Server worker stopped"))?
710    }
711
712    pub fn post_double_array(&self, name: &str, value: Vec<f64>) -> Result<()> {
713        let (reply_tx, reply_rx) = channel::bounded(1);
714        self.tx
715            .send(ManagerCommand::PostDoubleArray {
716                name: name.to_string(),
717                value,
718                reply: reply_tx,
719            })
720            .map_err(|_| PvxsError::new("Server worker stopped"))?;
721        reply_rx
722            .recv()
723            .map_err(|_| PvxsError::new("Server worker stopped"))?
724    }
725
726    pub fn post_int32(&self, name: &str, value: i32) -> Result<()> {
727        let (reply_tx, reply_rx) = channel::bounded(1);
728        self.tx
729            .send(ManagerCommand::PostInt32 {
730                name: name.to_string(),
731                value,
732                reply: reply_tx,
733            })
734            .map_err(|_| PvxsError::new("Server worker stopped"))?;
735        reply_rx
736            .recv()
737            .map_err(|_| PvxsError::new("Server worker stopped"))?
738    }
739
740    pub fn post_int32_array(&self, name: &str, value: Vec<i32>) -> Result<()> {
741        let (reply_tx, reply_rx) = channel::bounded(1);
742        self.tx
743            .send(ManagerCommand::PostInt32Array {
744                name: name.to_string(),
745                value,
746                reply: reply_tx,
747            })
748            .map_err(|_| PvxsError::new("Server worker stopped"))?;
749        reply_rx
750            .recv()
751            .map_err(|_| PvxsError::new("Server worker stopped"))?
752    }
753
754    pub fn post_string(&self, name: &str, value: &str) -> Result<()> {
755        let (reply_tx, reply_rx) = channel::bounded(1);
756        self.tx
757            .send(ManagerCommand::PostString {
758                name: name.to_string(),
759                value: value.to_string(),
760                reply: reply_tx,
761            })
762            .map_err(|_| PvxsError::new("Server worker stopped"))?;
763        reply_rx
764            .recv()
765            .map_err(|_| PvxsError::new("Server worker stopped"))?
766    }
767
768    pub fn post_string_array(&self, name: &str, value: Vec<String>) -> Result<()> {
769        let (reply_tx, reply_rx) = channel::bounded(1);
770        self.tx
771            .send(ManagerCommand::PostStringArray {
772                name: name.to_string(),
773                value,
774                reply: reply_tx,
775            })
776            .map_err(|_| PvxsError::new("Server worker stopped"))?;
777        reply_rx
778            .recv()
779            .map_err(|_| PvxsError::new("Server worker stopped"))?
780    }
781
782    pub fn post_enum(&self, name: &str, value: i16) -> Result<()> {
783        let (reply_tx, reply_rx) = channel::bounded(1);
784        self.tx
785            .send(ManagerCommand::PostEnum {
786                name: name.to_string(),
787                value,
788                reply: reply_tx,
789            })
790            .map_err(|_| PvxsError::new("Server worker stopped"))?;
791        reply_rx
792            .recv()
793            .map_err(|_| PvxsError::new("Server worker stopped"))?
794    }
795
796    pub fn remove_pv(&self, name: &str) -> Result<()> {
797        let (reply_tx, reply_rx) = channel::bounded(1);
798        self.tx
799            .send(ManagerCommand::Remove {
800                name: name.to_string(),
801                reply: reply_tx,
802            })
803            .map_err(|_| PvxsError::new("Server worker stopped"))?;
804        reply_rx
805            .recv()
806            .map_err(|_| PvxsError::new("Server worker stopped"))?
807    }
808
809    pub fn fetch_double(&self, name: &str) -> Result<FetchedDouble> {
810        let (reply_tx, reply_rx) = channel::bounded(1);
811        self.tx
812            .send(ManagerCommand::FetchDouble {
813                name: name.to_string(),
814                reply: reply_tx,
815            })
816            .map_err(|_| PvxsError::new("Server worker stopped"))?;
817        reply_rx
818            .recv()
819            .map_err(|_| PvxsError::new("Server worker stopped"))?
820    }
821
822    pub fn fetch_int32(&self, name: &str) -> Result<FetchedInt32> {
823        let (reply_tx, reply_rx) = channel::bounded(1);
824        self.tx
825            .send(ManagerCommand::FetchInt32 {
826                name: name.to_string(),
827                reply: reply_tx,
828            })
829            .map_err(|_| PvxsError::new("Server worker stopped"))?;
830        reply_rx
831            .recv()
832            .map_err(|_| PvxsError::new("Server worker stopped"))?
833    }
834
835    pub fn fetch_string(&self, name: &str) -> Result<FetchedString> {
836        let (reply_tx, reply_rx) = channel::bounded(1);
837        self.tx
838            .send(ManagerCommand::FetchString {
839                name: name.to_string(),
840                reply: reply_tx,
841            })
842            .map_err(|_| PvxsError::new("Server worker stopped"))?;
843        reply_rx
844            .recv()
845            .map_err(|_| PvxsError::new("Server worker stopped"))?
846    }
847
848    pub fn fetch_double_array(&self, name: &str) -> Result<FetchedDoubleArray> {
849        let (reply_tx, reply_rx) = channel::bounded(1);
850        self.tx
851            .send(ManagerCommand::FetchDoubleArray {
852                name: name.to_string(),
853                reply: reply_tx,
854            })
855            .map_err(|_| PvxsError::new("Server worker stopped"))?;
856        reply_rx
857            .recv()
858            .map_err(|_| PvxsError::new("Server worker stopped"))?
859    }
860
861    pub fn fetch_int32_array(&self, name: &str) -> Result<FetchedInt32Array> {
862        let (reply_tx, reply_rx) = channel::bounded(1);
863        self.tx
864            .send(ManagerCommand::FetchInt32Array {
865                name: name.to_string(),
866                reply: reply_tx,
867            })
868            .map_err(|_| PvxsError::new("Server worker stopped"))?;
869        reply_rx
870            .recv()
871            .map_err(|_| PvxsError::new("Server worker stopped"))?
872    }
873
874    pub fn fetch_string_array(&self, name: &str) -> Result<FetchedStringArray> {
875        let (reply_tx, reply_rx) = channel::bounded(1);
876        self.tx
877            .send(ManagerCommand::FetchStringArray {
878                name: name.to_string(),
879                reply: reply_tx,
880            })
881            .map_err(|_| PvxsError::new("Server worker stopped"))?;
882        reply_rx
883            .recv()
884            .map_err(|_| PvxsError::new("Server worker stopped"))?
885    }
886
887    pub fn fetch_enum(&self, name: &str) -> Result<FetchedEnum> {
888        let (reply_tx, reply_rx) = channel::bounded(1);
889        self.tx
890            .send(ManagerCommand::FetchEnum {
891                name: name.to_string(),
892                reply: reply_tx,
893            })
894            .map_err(|_| PvxsError::new("Server worker stopped"))?;
895        reply_rx
896            .recv()
897            .map_err(|_| PvxsError::new("Server worker stopped"))?
898    }
899}
900
901/// A PVXS server for hosting process variables with automatic alarm management
902///
903/// The Server provides managed PVs with automatic alarm handling, control limit
904/// validation, and value alarm checking. This is the recommended way to create
905/// EPICS servers in Rust.
906///
907/// # Features
908///
909/// - Automatic alarm computation based on control limits and value alarms
910/// - Thread-safe PV management with internal worker thread
911/// - Support for multiple data types (double, int32, string, enum, arrays)
912/// - Isolated or environment-configured operation
913///
914/// # Example
915///
916/// ```no_run
917/// use pvxs_sys::{Server, NTScalarMetadataBuilder, ControlMetadata};
918///
919/// let server = Server::start_from_env()?;
920///
921/// // Create a PV with control limits
922/// let metadata = NTScalarMetadataBuilder::new()
923///     .control(ControlMetadata {
924///         limit_low: 0.0,
925///         limit_high: 100.0,
926///         min_step: 0.1,
927///     });
928///
929/// server.create_pv_double("test:pv", 50.0, metadata)?;
930/// server.post_double("test:pv", 75.0)?;  // Validated and with alarms
931///
932/// server.stop_drop()?;
933/// # Ok::<(), pvxs_sys::PvxsError>(())
934/// ```
935pub struct Server {
936    handle: ServerHandle,
937    join: Option<thread::JoinHandle<()>>,
938}
939
940impl Server {
941    /// Start a server using environment configuration
942    ///
943    /// Creates and starts a server that reads EPICS network configuration
944    /// from environment variables.
945    ///
946    /// # Errors
947    ///
948    /// Returns an error if the server cannot be created or started.
949    pub fn start_from_env() -> Result<Self> {
950        Self::start_inner(false)
951    }
952
953    /// Start an isolated server for testing
954    ///
955    /// Creates and starts a server with system-assigned ports, ideal for
956    /// unit tests and parallel test execution.
957    ///
958    /// # Errors
959    ///
960    /// Returns an error if the server cannot be created or started.
961    pub fn start_isolated() -> Result<Self> {
962        Self::start_inner(true)
963    }
964
965    /// Get a cloneable handle to this server
966    ///
967    /// The handle can be used from other threads to interact with the server.
968    pub fn handle(&self) -> ServerHandle {
969        self.handle.clone()
970    }
971
972    pub fn tcp_port(&self) -> u16 {
973        self.handle.tcp_port()
974    }
975
976    pub fn udp_port(&self) -> u16 {
977        self.handle.udp_port()
978    }
979
980    pub fn create_pv_double(
981        &self,
982        name: &str,
983        initial: f64,
984        metadata: NTScalarMetadataBuilder,
985    ) -> Result<()> {
986        self.handle.create_pv_double(name, initial, metadata)
987    }
988
989    pub fn create_pv_double_array(
990        &self,
991        name: &str,
992        initial: Vec<f64>,
993        metadata: NTScalarMetadataBuilder,
994    ) -> Result<()> {
995        self.handle.create_pv_double_array(name, initial, metadata)
996    }
997
998    pub fn create_pv_int32(
999        &self,
1000        name: &str,
1001        initial: i32,
1002        metadata: NTScalarMetadataBuilder,
1003    ) -> Result<()> {
1004        self.handle.create_pv_int32(name, initial, metadata)
1005    }
1006
1007    pub fn create_pv_int32_array(
1008        &self,
1009        name: &str,
1010        initial: Vec<i32>,
1011        metadata: NTScalarMetadataBuilder,
1012    ) -> Result<()> {
1013        self.handle.create_pv_int32_array(name, initial, metadata)
1014    }
1015
1016    pub fn create_pv_string(
1017        &self,
1018        name: &str,
1019        initial: &str,
1020        metadata: NTScalarMetadataBuilder,
1021    ) -> Result<()> {
1022        self.handle.create_pv_string(name, initial, metadata)
1023    }
1024
1025    pub fn create_pv_string_array(
1026        &self,
1027        name: &str,
1028        initial: Vec<String>,
1029        metadata: NTScalarMetadataBuilder,
1030    ) -> Result<()> {
1031        self.handle.create_pv_string_array(name, initial, metadata)
1032    }
1033
1034    pub fn create_pv_enum(
1035        &self,
1036        name: &str,
1037        choices: Vec<&str>,
1038        selected_index: i16,
1039        metadata: NTEnumMetadataBuilder,
1040    ) -> Result<()> {
1041        self.handle
1042            .create_pv_enum(name, choices, selected_index, metadata)
1043    }
1044
1045    pub fn post_double(&self, name: &str, value: f64) -> Result<()> {
1046        self.handle.post_double(name, value)
1047    }
1048
1049    pub fn post_double_array(&self, name: &str, value: Vec<f64>) -> Result<()> {
1050        self.handle.post_double_array(name, value)
1051    }
1052
1053    pub fn post_int32(&self, name: &str, value: i32) -> Result<()> {
1054        self.handle.post_int32(name, value)
1055    }
1056
1057    pub fn post_int32_array(&self, name: &str, value: Vec<i32>) -> Result<()> {
1058        self.handle.post_int32_array(name, value)
1059    }
1060
1061    pub fn post_string(&self, name: &str, value: &str) -> Result<()> {
1062        self.handle.post_string(name, value)
1063    }
1064
1065    pub fn post_string_array(&self, name: &str, value: Vec<String>) -> Result<()> {
1066        self.handle.post_string_array(name, value)
1067    }
1068
1069    pub fn post_enum(&self, name: &str, value: i16) -> Result<()> {
1070        self.handle.post_enum(name, value)
1071    }
1072
1073    pub fn remove_pv(&self, name: &str) -> Result<()> {
1074        self.handle.remove_pv(name)
1075    }
1076
1077    pub fn fetch_double(&self, name: &str) -> Result<FetchedDouble> {
1078        self.handle.fetch_double(name)
1079    }
1080
1081    pub fn fetch_int32(&self, name: &str) -> Result<FetchedInt32> {
1082        self.handle.fetch_int32(name)
1083    }
1084
1085    pub fn fetch_string(&self, name: &str) -> Result<FetchedString> {
1086        self.handle.fetch_string(name)
1087    }
1088
1089    pub fn fetch_double_array(&self, name: &str) -> Result<FetchedDoubleArray> {
1090        self.handle.fetch_double_array(name)
1091    }
1092
1093    pub fn fetch_int32_array(&self, name: &str) -> Result<FetchedInt32Array> {
1094        self.handle.fetch_int32_array(name)
1095    }
1096
1097    pub fn fetch_string_array(&self, name: &str) -> Result<FetchedStringArray> {
1098        self.handle.fetch_string_array(name)
1099    }
1100
1101    pub fn fetch_enum(&self, name: &str) -> Result<FetchedEnum> {
1102        self.handle.fetch_enum(name)
1103    }
1104
1105    /// Indirectly calls inner stop through the manager thread, ensuring proper shutdown and resource cleanup
1106    /// Once Stop command is sent the worker thread will exit, however it will destroy the ServerImpl and the
1107    /// entire pvs hashmap so the all underlying C++ objects are freed correctly.
1108    ///
1109    /// Call start_from_env or start_isolated again and create new PVs to start a new server instance if needed after stopping.
1110    pub fn stop_drop(mut self) -> Result<()> {
1111        let (reply_tx, reply_rx) = channel::bounded(1);
1112        self.handle
1113            .tx
1114            .send(ManagerCommand::Stop { reply: reply_tx })
1115            .map_err(|_| PvxsError::new("Server worker stopped"))?;
1116        let result = reply_rx
1117            .recv()
1118            .map_err(|_| PvxsError::new("Server worker stopped"))?;
1119        if let Some(join) = self.join.take() {
1120            let _ = join.join();
1121        }
1122        result
1123    }
1124
1125    fn start_inner(isolated: bool) -> Result<Self> {
1126        let (tx, rx) = channel::unbounded::<ManagerCommand>();
1127        let (ready_tx, ready_rx) = channel::bounded::<Result<(u16, u16)>>(1);
1128
1129        let join = thread::spawn(move || {
1130            let mut server = if isolated {
1131                match ServerImpl::create_isolated() {
1132                    Ok(s) => s,
1133                    Err(e) => {
1134                        let _ = ready_tx.send(Err(e));
1135                        return;
1136                    }
1137                }
1138            } else {
1139                match ServerImpl::from_env() {
1140                    Ok(s) => s,
1141                    Err(e) => {
1142                        let _ = ready_tx.send(Err(e));
1143                        return;
1144                    }
1145                }
1146            };
1147
1148            if let Err(e) = server.start() {
1149                let _ = ready_tx.send(Err(e));
1150                return;
1151            }
1152
1153            let _ = ready_tx.send(Ok((server.tcp_port(), server.udp_port())));
1154
1155            let mut pvs: HashMap<String, ManagedPv> = HashMap::new();
1156
1157            while let Ok(cmd) = rx.recv() {
1158                match cmd {
1159                    ManagerCommand::CreateDouble {
1160                        name,
1161                        initial,
1162                        metadata,
1163                        reply,
1164                    } => {
1165                        let result = if pvs.contains_key(&name) {
1166                            Err(PvxsError::new("PV already exists"))
1167                        } else {
1168                            let alarm = AlarmConfig {
1169                                control: metadata.control.clone(),
1170                                alarm_metadata: metadata.alarm_metadata.clone(),
1171                            };
1172                            // Compute alarm for initial value
1173                            let alarm_result = compute_alarm_for_scalar(initial, &alarm);
1174                            // Update metadata with computed alarm
1175                            let mut metadata_with_alarm = metadata;
1176                            metadata_with_alarm.alarm_severity = alarm_result.severity;
1177                            metadata_with_alarm.alarm_status = alarm_result.status;
1178                            metadata_with_alarm.alarm_message = alarm_result.message.clone();
1179
1180                            match server.create_pv_double(&name, initial, metadata_with_alarm) {
1181                                Ok(pv) => {
1182                                    pvs.insert(
1183                                        name,
1184                                        ManagedPv::Double {
1185                                            pv,
1186                                            alarm,
1187                                            last: initial,
1188                                        },
1189                                    );
1190                                    Ok(())
1191                                }
1192                                Err(e) => Err(e),
1193                            }
1194                        };
1195                        let _ = reply.send(result);
1196                    }
1197                    ManagerCommand::CreateDoubleArray {
1198                        name,
1199                        initial,
1200                        metadata,
1201                        reply,
1202                    } => {
1203                        let result = if pvs.contains_key(&name) {
1204                            Err(PvxsError::new("PV already exists"))
1205                        } else {
1206                            match server.create_pv_double_array(&name, initial, metadata) {
1207                                Ok(pv) => {
1208                                    pvs.insert(name, ManagedPv::DoubleArray(pv));
1209                                    Ok(())
1210                                }
1211                                Err(e) => Err(e),
1212                            }
1213                        };
1214                        let _ = reply.send(result);
1215                    }
1216                    ManagerCommand::CreateInt32 {
1217                        name,
1218                        initial,
1219                        metadata,
1220                        reply,
1221                    } => {
1222                        let result = if pvs.contains_key(&name) {
1223                            Err(PvxsError::new("PV already exists"))
1224                        } else {
1225                            let alarm = AlarmConfig {
1226                                control: metadata.control.clone(),
1227                                alarm_metadata: metadata.alarm_metadata.clone(),
1228                            };
1229                            // Compute alarm for initial value
1230                            let alarm_result = compute_alarm_for_scalar(initial as f64, &alarm);
1231                            // Update metadata with computed alarm
1232                            let mut metadata_with_alarm = metadata;
1233                            metadata_with_alarm.alarm_severity = alarm_result.severity;
1234                            metadata_with_alarm.alarm_status = alarm_result.status;
1235                            metadata_with_alarm.alarm_message = alarm_result.message.clone();
1236
1237                            match server.create_pv_int32(&name, initial, metadata_with_alarm) {
1238                                Ok(pv) => {
1239                                    pvs.insert(
1240                                        name,
1241                                        ManagedPv::Int32 {
1242                                            pv,
1243                                            alarm,
1244                                            last: initial,
1245                                        },
1246                                    );
1247                                    Ok(())
1248                                }
1249                                Err(e) => Err(e),
1250                            }
1251                        };
1252                        let _ = reply.send(result);
1253                    }
1254                    ManagerCommand::CreateInt32Array {
1255                        name,
1256                        initial,
1257                        metadata,
1258                        reply,
1259                    } => {
1260                        let result = if pvs.contains_key(&name) {
1261                            Err(PvxsError::new("PV already exists"))
1262                        } else {
1263                            match server.create_pv_int32_array(&name, initial, metadata) {
1264                                Ok(pv) => {
1265                                    pvs.insert(name, ManagedPv::Int32Array(pv));
1266                                    Ok(())
1267                                }
1268                                Err(e) => Err(e),
1269                            }
1270                        };
1271                        let _ = reply.send(result);
1272                    }
1273                    ManagerCommand::CreateString {
1274                        name,
1275                        initial,
1276                        metadata,
1277                        reply,
1278                    } => {
1279                        let result = if pvs.contains_key(&name) {
1280                            Err(PvxsError::new("PV already exists"))
1281                        } else {
1282                            match server.create_pv_string(&name, &initial, metadata) {
1283                                Ok(pv) => {
1284                                    pvs.insert(name, ManagedPv::String(pv));
1285                                    Ok(())
1286                                }
1287                                Err(e) => Err(e),
1288                            }
1289                        };
1290                        let _ = reply.send(result);
1291                    }
1292                    ManagerCommand::CreateStringArray {
1293                        name,
1294                        initial,
1295                        metadata,
1296                        reply,
1297                    } => {
1298                        let result = if pvs.contains_key(&name) {
1299                            Err(PvxsError::new("PV already exists"))
1300                        } else {
1301                            match server.create_pv_string_array(&name, initial, metadata) {
1302                                Ok(pv) => {
1303                                    pvs.insert(name, ManagedPv::StringArray(pv));
1304                                    Ok(())
1305                                }
1306                                Err(e) => Err(e),
1307                            }
1308                        };
1309                        let _ = reply.send(result);
1310                    }
1311                    ManagerCommand::CreateEnum {
1312                        name,
1313                        choices,
1314                        selected_index,
1315                        metadata,
1316                        reply,
1317                    } => {
1318                        let result = if pvs.contains_key(&name) {
1319                            Err(PvxsError::new("PV already exists"))
1320                        } else {
1321                            let choices_refs: Vec<&str> =
1322                                choices.iter().map(|s| s.as_str()).collect();
1323                            match server.create_pv_enum(
1324                                &name,
1325                                choices_refs,
1326                                selected_index,
1327                                metadata,
1328                            ) {
1329                                Ok(pv) => {
1330                                    pvs.insert(name, ManagedPv::PvEnum(pv));
1331                                    Ok(())
1332                                }
1333                                Err(e) => Err(e),
1334                            }
1335                        };
1336                        let _ = reply.send(result);
1337                    }
1338                    ManagerCommand::PostDouble { name, value, reply } => {
1339                        let result = match pvs.get_mut(&name) {
1340                            Some(ManagedPv::Double { pv, alarm, last }) => {
1341                                let alarm_result = compute_alarm_for_scalar(value, alarm);
1342                                // If not allowed, fall back to the live value currently in the PV
1343                                // (which may have been written by a client PUT since the last Rust post)
1344                                let post_value = if alarm_result.allow {
1345                                    value
1346                                } else {
1347                                    pv.fetch()
1348                                        .and_then(|v| v.get_field_double("value"))
1349                                        .unwrap_or(*last)
1350                                };
1351                                let result = pv.post_double_with_alarm(
1352                                    post_value,
1353                                    alarm_result.severity,
1354                                    alarm_result.status,
1355                                    alarm_result.message,
1356                                );
1357                                if result.is_ok() && alarm_result.allow {
1358                                    *last = post_value;
1359                                }
1360                                result
1361                            }
1362                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1363                        };
1364                        let _ = reply.send(result);
1365                    }
1366                    ManagerCommand::PostDoubleArray { name, value, reply } => {
1367                        let result = match pvs.get_mut(&name) {
1368                            Some(ManagedPv::DoubleArray(pv)) => pv.post_double_array(&value),
1369                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1370                        };
1371                        let _ = reply.send(result);
1372                    }
1373                    ManagerCommand::PostInt32 { name, value, reply } => {
1374                        let result = match pvs.get_mut(&name) {
1375                            Some(ManagedPv::Int32 { pv, alarm, last }) => {
1376                                let alarm_result = compute_alarm_for_scalar(value as f64, alarm);
1377                                // If not allowed, fall back to the live value currently in the PV
1378                                // (which may have been written by a client PUT since the last Rust post)
1379                                let post_value = if alarm_result.allow {
1380                                    value
1381                                } else {
1382                                    pv.fetch()
1383                                        .and_then(|v| v.get_field_int32("value"))
1384                                        .unwrap_or(*last)
1385                                };
1386                                let result = pv.post_int32_with_alarm(
1387                                    post_value,
1388                                    alarm_result.severity,
1389                                    alarm_result.status,
1390                                    alarm_result.message,
1391                                );
1392                                if result.is_ok() && alarm_result.allow {
1393                                    *last = post_value;
1394                                }
1395                                result
1396                            }
1397                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1398                        };
1399                        let _ = reply.send(result);
1400                    }
1401                    ManagerCommand::PostInt32Array { name, value, reply } => {
1402                        let result = match pvs.get_mut(&name) {
1403                            Some(ManagedPv::Int32Array(pv)) => pv.post_int32_array(&value),
1404                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1405                        };
1406                        let _ = reply.send(result);
1407                    }
1408                    ManagerCommand::PostString { name, value, reply } => {
1409                        let result = match pvs.get_mut(&name) {
1410                            Some(ManagedPv::String(pv)) => pv.post_string(&value),
1411                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1412                        };
1413                        let _ = reply.send(result);
1414                    }
1415                    ManagerCommand::PostStringArray { name, value, reply } => {
1416                        let result = match pvs.get_mut(&name) {
1417                            Some(ManagedPv::StringArray(pv)) => pv.post_string_array(&value),
1418                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1419                        };
1420                        let _ = reply.send(result);
1421                    }
1422                    ManagerCommand::PostEnum { name, value, reply } => {
1423                        let result = match pvs.get_mut(&name) {
1424                            Some(ManagedPv::PvEnum(pv)) => pv.post_enum(value),
1425                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1426                        };
1427                        let _ = reply.send(result);
1428                    }
1429                    ManagerCommand::Remove { name, reply } => {
1430                        let result = if pvs.remove(&name).is_some() {
1431                            server.remove_pv(&name)
1432                        } else {
1433                            Err(PvxsError::new("PV not found"))
1434                        };
1435                        let _ = reply.send(result);
1436                    }
1437                    ManagerCommand::FetchDouble { name, reply } => {
1438                        let result = match pvs.get(&name) {
1439                            Some(ManagedPv::Double { pv, .. }) => {
1440                                pv.fetch().and_then(|v| {
1441                                    // Extract display metadata if present
1442                                    let display_metadata = (|| -> Option<DisplayMetadata> {
1443                                        Some(DisplayMetadata {
1444                                            limit_low: v.get_field_int32("display.limitLow").ok()?
1445                                                as i64,
1446                                            limit_high: v
1447                                                .get_field_int32("display.limitHigh")
1448                                                .ok()?
1449                                                as i64,
1450                                            description: v
1451                                                .get_field_string("display.description")
1452                                                .ok()?,
1453                                            units: v.get_field_string("display.units").ok()?,
1454                                            precision: v
1455                                                .get_field_int32("display.precision")
1456                                                .ok()?,
1457                                        })
1458                                    })();
1459
1460                                    // Extract control metadata if present
1461                                    let control_metadata = (|| -> Option<ControlMetadata> {
1462                                        Some(ControlMetadata {
1463                                            limit_low: v
1464                                                .get_field_double("control.limitLow")
1465                                                .ok()?,
1466                                            limit_high: v
1467                                                .get_field_double("control.limitHigh")
1468                                                .ok()?,
1469                                            min_step: v.get_field_double("control.minStep").ok()?,
1470                                        })
1471                                    })();
1472
1473                                    // Extract alarm metadata if present
1474                                    let alarm_metadata = (|| -> Option<AlarmMetadata> {
1475                                        Some(AlarmMetadata {
1476                                            active: v.get_field_int32("valueAlarm.active").ok()?
1477                                                != 0,
1478                                            low_alarm_limit: v
1479                                                .get_field_double("valueAlarm.lowAlarmLimit")
1480                                                .ok()?,
1481                                            low_warning_limit: v
1482                                                .get_field_double("valueAlarm.lowWarningLimit")
1483                                                .ok()?,
1484                                            high_warning_limit: v
1485                                                .get_field_double("valueAlarm.highWarningLimit")
1486                                                .ok()?,
1487                                            high_alarm_limit: v
1488                                                .get_field_double("valueAlarm.highAlarmLimit")
1489                                                .ok()?,
1490                                            low_alarm_severity: AlarmSeverity::from(
1491                                                v.get_field_int32("valueAlarm.lowAlarmSeverity")
1492                                                    .ok()?,
1493                                            ),
1494                                            low_warning_severity: AlarmSeverity::from(
1495                                                v.get_field_int32("valueAlarm.lowWarningSeverity")
1496                                                    .ok()?,
1497                                            ),
1498                                            high_warning_severity: AlarmSeverity::from(
1499                                                v.get_field_int32("valueAlarm.highWarningSeverity")
1500                                                    .ok()?,
1501                                            ),
1502                                            high_alarm_severity: AlarmSeverity::from(
1503                                                v.get_field_int32("valueAlarm.highAlarmSeverity")
1504                                                    .ok()?,
1505                                            ),
1506                                            hysteresis: v
1507                                                .get_field_int32("valueAlarm.hysteresis")
1508                                                .ok()?
1509                                                as u8,
1510                                        })
1511                                    })();
1512
1513                                    Ok(FetchedDouble {
1514                                        value: v.get_field_double("value")?,
1515                                        alarm_severity: AlarmSeverity::from(
1516                                            v.get_field_int32("alarm.severity").unwrap_or(0),
1517                                        ),
1518                                        alarm_status: AlarmStatus::from(
1519                                            v.get_field_int32("alarm.status").unwrap_or(0),
1520                                        ),
1521                                        alarm_message: v
1522                                            .get_field_string("alarm.message")
1523                                            .unwrap_or_default(),
1524                                        display_metadata,
1525                                        control_metadata,
1526                                        alarm_metadata,
1527                                    })
1528                                })
1529                            }
1530                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1531                        };
1532                        let _ = reply.send(result);
1533                    }
1534                    ManagerCommand::FetchInt32 { name, reply } => {
1535                        let result = match pvs.get(&name) {
1536                            Some(ManagedPv::Int32 { pv, .. }) => {
1537                                pv.fetch().and_then(|v| {
1538                                    // Extract display metadata if present
1539                                    let display_metadata = (|| -> Option<DisplayMetadata> {
1540                                        Some(DisplayMetadata {
1541                                            limit_low: v.get_field_int32("display.limitLow").ok()?
1542                                                as i64,
1543                                            limit_high: v
1544                                                .get_field_int32("display.limitHigh")
1545                                                .ok()?
1546                                                as i64,
1547                                            description: v
1548                                                .get_field_string("display.description")
1549                                                .ok()?,
1550                                            units: v.get_field_string("display.units").ok()?,
1551                                            precision: v
1552                                                .get_field_int32("display.precision")
1553                                                .ok()?,
1554                                        })
1555                                    })();
1556
1557                                    // Extract control metadata if present
1558                                    let control_metadata = (|| -> Option<ControlMetadata> {
1559                                        Some(ControlMetadata {
1560                                            limit_low: v
1561                                                .get_field_double("control.limitLow")
1562                                                .ok()?,
1563                                            limit_high: v
1564                                                .get_field_double("control.limitHigh")
1565                                                .ok()?,
1566                                            min_step: v.get_field_double("control.minStep").ok()?,
1567                                        })
1568                                    })();
1569
1570                                    // Extract alarm metadata if present
1571                                    let alarm_metadata = (|| -> Option<AlarmMetadata> {
1572                                        Some(AlarmMetadata {
1573                                            active: v.get_field_int32("valueAlarm.active").ok()?
1574                                                != 0,
1575                                            low_alarm_limit: v
1576                                                .get_field_double("valueAlarm.lowAlarmLimit")
1577                                                .ok()?,
1578                                            low_warning_limit: v
1579                                                .get_field_double("valueAlarm.lowWarningLimit")
1580                                                .ok()?,
1581                                            high_warning_limit: v
1582                                                .get_field_double("valueAlarm.highWarningLimit")
1583                                                .ok()?,
1584                                            high_alarm_limit: v
1585                                                .get_field_double("valueAlarm.highAlarmLimit")
1586                                                .ok()?,
1587                                            low_alarm_severity: AlarmSeverity::from(
1588                                                v.get_field_int32("valueAlarm.lowAlarmSeverity")
1589                                                    .ok()?,
1590                                            ),
1591                                            low_warning_severity: AlarmSeverity::from(
1592                                                v.get_field_int32("valueAlarm.lowWarningSeverity")
1593                                                    .ok()?,
1594                                            ),
1595                                            high_warning_severity: AlarmSeverity::from(
1596                                                v.get_field_int32("valueAlarm.highWarningSeverity")
1597                                                    .ok()?,
1598                                            ),
1599                                            high_alarm_severity: AlarmSeverity::from(
1600                                                v.get_field_int32("valueAlarm.highAlarmSeverity")
1601                                                    .ok()?,
1602                                            ),
1603                                            hysteresis: v
1604                                                .get_field_int32("valueAlarm.hysteresis")
1605                                                .ok()?
1606                                                as u8,
1607                                        })
1608                                    })();
1609
1610                                    Ok(FetchedInt32 {
1611                                        value: v.get_field_int32("value")?,
1612                                        alarm_severity: AlarmSeverity::from(
1613                                            v.get_field_int32("alarm.severity").unwrap_or(0),
1614                                        ),
1615                                        alarm_status: AlarmStatus::from(
1616                                            v.get_field_int32("alarm.status").unwrap_or(0),
1617                                        ),
1618                                        alarm_message: v
1619                                            .get_field_string("alarm.message")
1620                                            .unwrap_or_default(),
1621                                        display_metadata,
1622                                        control_metadata,
1623                                        alarm_metadata,
1624                                    })
1625                                })
1626                            }
1627                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1628                        };
1629                        let _ = reply.send(result);
1630                    }
1631                    ManagerCommand::FetchString { name, reply } => {
1632                        let result = match pvs.get(&name) {
1633                            Some(ManagedPv::String(pv)) => pv.fetch().and_then(|v| {
1634                                Ok(FetchedString {
1635                                    value: v.get_field_string("value")?,
1636                                    alarm_severity: AlarmSeverity::from(
1637                                        v.get_field_int32("alarm.severity").unwrap_or(0),
1638                                    ),
1639                                    alarm_status: AlarmStatus::from(
1640                                        v.get_field_int32("alarm.status").unwrap_or(0),
1641                                    ),
1642                                    alarm_message: v
1643                                        .get_field_string("alarm.message")
1644                                        .unwrap_or_default(),
1645                                })
1646                            }),
1647                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1648                        };
1649                        let _ = reply.send(result);
1650                    }
1651                    ManagerCommand::FetchDoubleArray { name, reply } => {
1652                        let result = match pvs.get(&name) {
1653                            Some(ManagedPv::DoubleArray(pv)) => pv.fetch().and_then(|v| {
1654                                let display_metadata = (|| -> Option<DisplayMetadata> {
1655                                    Some(DisplayMetadata {
1656                                        limit_low: v.get_field_int32("display.limitLow").ok()?
1657                                            as i64,
1658                                        limit_high: v.get_field_int32("display.limitHigh").ok()?
1659                                            as i64,
1660                                        description: v
1661                                            .get_field_string("display.description")
1662                                            .ok()?,
1663                                        units: v.get_field_string("display.units").ok()?,
1664                                        precision: v.get_field_int32("display.precision").ok()?,
1665                                    })
1666                                })();
1667                                let control_metadata = (|| -> Option<ControlMetadata> {
1668                                    Some(ControlMetadata {
1669                                        limit_low: v.get_field_double("control.limitLow").ok()?,
1670                                        limit_high: v.get_field_double("control.limitHigh").ok()?,
1671                                        min_step: v.get_field_double("control.minStep").ok()?,
1672                                    })
1673                                })();
1674                                let alarm_metadata = (|| -> Option<AlarmMetadata> {
1675                                    Some(AlarmMetadata {
1676                                        active: v.get_field_int32("valueAlarm.active").ok()? != 0,
1677                                        low_alarm_limit: v
1678                                            .get_field_double("valueAlarm.lowAlarmLimit")
1679                                            .ok()?,
1680                                        low_warning_limit: v
1681                                            .get_field_double("valueAlarm.lowWarningLimit")
1682                                            .ok()?,
1683                                        high_warning_limit: v
1684                                            .get_field_double("valueAlarm.highWarningLimit")
1685                                            .ok()?,
1686                                        high_alarm_limit: v
1687                                            .get_field_double("valueAlarm.highAlarmLimit")
1688                                            .ok()?,
1689                                        low_alarm_severity: AlarmSeverity::from(
1690                                            v.get_field_int32("valueAlarm.lowAlarmSeverity")
1691                                                .ok()?,
1692                                        ),
1693                                        low_warning_severity: AlarmSeverity::from(
1694                                            v.get_field_int32("valueAlarm.lowWarningSeverity")
1695                                                .ok()?,
1696                                        ),
1697                                        high_warning_severity: AlarmSeverity::from(
1698                                            v.get_field_int32("valueAlarm.highWarningSeverity")
1699                                                .ok()?,
1700                                        ),
1701                                        high_alarm_severity: AlarmSeverity::from(
1702                                            v.get_field_int32("valueAlarm.highAlarmSeverity")
1703                                                .ok()?,
1704                                        ),
1705                                        hysteresis: v
1706                                            .get_field_int32("valueAlarm.hysteresis")
1707                                            .ok()?
1708                                            as u8,
1709                                    })
1710                                })();
1711                                Ok(FetchedDoubleArray {
1712                                    value: v.get_field_double_array("value")?,
1713                                    alarm_severity: AlarmSeverity::from(
1714                                        v.get_field_int32("alarm.severity").unwrap_or(0),
1715                                    ),
1716                                    alarm_status: AlarmStatus::from(
1717                                        v.get_field_int32("alarm.status").unwrap_or(0),
1718                                    ),
1719                                    alarm_message: v
1720                                        .get_field_string("alarm.message")
1721                                        .unwrap_or_default(),
1722                                    display_metadata,
1723                                    control_metadata,
1724                                    alarm_metadata,
1725                                })
1726                            }),
1727                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1728                        };
1729                        let _ = reply.send(result);
1730                    }
1731                    ManagerCommand::FetchInt32Array { name, reply } => {
1732                        let result = match pvs.get(&name) {
1733                            Some(ManagedPv::Int32Array(pv)) => pv.fetch().and_then(|v| {
1734                                let display_metadata = (|| -> Option<DisplayMetadata> {
1735                                    Some(DisplayMetadata {
1736                                        limit_low: v.get_field_int32("display.limitLow").ok()?
1737                                            as i64,
1738                                        limit_high: v.get_field_int32("display.limitHigh").ok()?
1739                                            as i64,
1740                                        description: v
1741                                            .get_field_string("display.description")
1742                                            .ok()?,
1743                                        units: v.get_field_string("display.units").ok()?,
1744                                        precision: v.get_field_int32("display.precision").ok()?,
1745                                    })
1746                                })();
1747                                let control_metadata = (|| -> Option<ControlMetadata> {
1748                                    Some(ControlMetadata {
1749                                        limit_low: v.get_field_double("control.limitLow").ok()?,
1750                                        limit_high: v.get_field_double("control.limitHigh").ok()?,
1751                                        min_step: v.get_field_double("control.minStep").ok()?,
1752                                    })
1753                                })();
1754                                let alarm_metadata = (|| -> Option<AlarmMetadata> {
1755                                    Some(AlarmMetadata {
1756                                        active: v.get_field_int32("valueAlarm.active").ok()? != 0,
1757                                        low_alarm_limit: v
1758                                            .get_field_double("valueAlarm.lowAlarmLimit")
1759                                            .ok()?,
1760                                        low_warning_limit: v
1761                                            .get_field_double("valueAlarm.lowWarningLimit")
1762                                            .ok()?,
1763                                        high_warning_limit: v
1764                                            .get_field_double("valueAlarm.highWarningLimit")
1765                                            .ok()?,
1766                                        high_alarm_limit: v
1767                                            .get_field_double("valueAlarm.highAlarmLimit")
1768                                            .ok()?,
1769                                        low_alarm_severity: AlarmSeverity::from(
1770                                            v.get_field_int32("valueAlarm.lowAlarmSeverity")
1771                                                .ok()?,
1772                                        ),
1773                                        low_warning_severity: AlarmSeverity::from(
1774                                            v.get_field_int32("valueAlarm.lowWarningSeverity")
1775                                                .ok()?,
1776                                        ),
1777                                        high_warning_severity: AlarmSeverity::from(
1778                                            v.get_field_int32("valueAlarm.highWarningSeverity")
1779                                                .ok()?,
1780                                        ),
1781                                        high_alarm_severity: AlarmSeverity::from(
1782                                            v.get_field_int32("valueAlarm.highAlarmSeverity")
1783                                                .ok()?,
1784                                        ),
1785                                        hysteresis: v
1786                                            .get_field_int32("valueAlarm.hysteresis")
1787                                            .ok()?
1788                                            as u8,
1789                                    })
1790                                })();
1791                                Ok(FetchedInt32Array {
1792                                    value: v.get_field_int32_array("value")?,
1793                                    alarm_severity: AlarmSeverity::from(
1794                                        v.get_field_int32("alarm.severity").unwrap_or(0),
1795                                    ),
1796                                    alarm_status: AlarmStatus::from(
1797                                        v.get_field_int32("alarm.status").unwrap_or(0),
1798                                    ),
1799                                    alarm_message: v
1800                                        .get_field_string("alarm.message")
1801                                        .unwrap_or_default(),
1802                                    display_metadata,
1803                                    control_metadata,
1804                                    alarm_metadata,
1805                                })
1806                            }),
1807                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1808                        };
1809                        let _ = reply.send(result);
1810                    }
1811                    ManagerCommand::FetchStringArray { name, reply } => {
1812                        let result = match pvs.get(&name) {
1813                            Some(ManagedPv::StringArray(pv)) => pv.fetch().and_then(|v| {
1814                                Ok(FetchedStringArray {
1815                                    value: v.get_field_string_array("value")?,
1816                                    alarm_severity: AlarmSeverity::from(
1817                                        v.get_field_int32("alarm.severity").unwrap_or(0),
1818                                    ),
1819                                    alarm_status: AlarmStatus::from(
1820                                        v.get_field_int32("alarm.status").unwrap_or(0),
1821                                    ),
1822                                    alarm_message: v
1823                                        .get_field_string("alarm.message")
1824                                        .unwrap_or_default(),
1825                                })
1826                            }),
1827                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1828                        };
1829                        let _ = reply.send(result);
1830                    }
1831                    ManagerCommand::FetchEnum { name, reply } => {
1832                        let result = match pvs.get(&name) {
1833                            Some(ManagedPv::PvEnum(pv)) => pv.fetch().and_then(|v| {
1834                                Ok(FetchedEnum {
1835                                    value: v.get_field_enum("value.index")?,
1836                                    value_choices: v
1837                                        .get_field_string_array("value.choices")
1838                                        .unwrap_or_default(),
1839                                    alarm_severity: AlarmSeverity::from(
1840                                        v.get_field_int32("alarm.severity").unwrap_or(0),
1841                                    ),
1842                                    alarm_status: AlarmStatus::from(
1843                                        v.get_field_int32("alarm.status").unwrap_or(0),
1844                                    ),
1845                                    alarm_message: v
1846                                        .get_field_string("alarm.message")
1847                                        .unwrap_or_default(),
1848                                })
1849                            }),
1850                            _ => Err(PvxsError::new("PV not found or type mismatch")),
1851                        };
1852                        let _ = reply.send(result);
1853                    }
1854                    ManagerCommand::Stop { reply } => {
1855                        let result = server.stop();
1856                        let _ = reply.send(result);
1857                        break;
1858                    }
1859                }
1860            }
1861        });
1862
1863        let (tcp_port, udp_port) = ready_rx
1864            .recv()
1865            .map_err(|_| PvxsError::new("Server failed to start"))??;
1866
1867        Ok(Self {
1868            handle: ServerHandle {
1869                tx,
1870                tcp_port,
1871                udp_port,
1872            },
1873            join: Some(join),
1874        })
1875    }
1876}
1877
1878/// A shared process variable that can be hosted by a server
1879///
1880/// SharedPVs represent individual process variables with typed values
1881/// that can be accessed by EPICS clients.
1882///
1883/// # Example
1884///
1885/// ```ignore
1886/// use pvxs_sys::SharedPV;
1887///
1888/// let mut pv = SharedPV::create_mailbox()?;
1889/// // Note: open_double is internal API, use Server::create_pv_* methods instead
1890///
1891/// // Update the value
1892/// pv.post_double(99.9)?;
1893///
1894/// // Get current value
1895/// let value = pv.fetch()?;
1896/// println!("Current value: {}", value);
1897/// # Ok::<(), pvxs_sys::PvxsError>(())
1898/// ```
1899pub struct SharedPV {
1900    inner: UniquePtr<bridge::SharedPVWrapper>,
1901}
1902
1903impl SharedPV {
1904    /// Create a mailbox SharedPV
1905    ///
1906    /// Mailbox PVs support both read and write operations by clients.
1907    pub fn create_mailbox() -> Result<Self> {
1908        let inner = bridge::shared_pv_create_mailbox()?;
1909        Ok(Self { inner })
1910    }
1911
1912    /// Create a readonly SharedPV
1913    ///
1914    /// Readonly PVs only support read operations by clients.
1915    pub fn create_readonly() -> Result<Self> {
1916        let inner = bridge::shared_pv_create_readonly()?;
1917        Ok(Self { inner })
1918    }
1919
1920    /// Open the PV with a double value and metadata
1921    ///
1922    /// # Arguments
1923    ///
1924    /// * `initial_value` - The initial value for the PV
1925    /// * `metadata` - Metadata builder for the scalar PV
1926    ///
1927    /// # Example
1928    ///
1929    /// ```ignore
1930    /// # use pvxs_sys::{SharedPV, NTScalarMetadataBuilder, DisplayMetadata};
1931    /// // Note: open_double is internal API
1932    /// // Use Server::create_pv_double instead for public API
1933    /// let mut pv = SharedPV::create_mailbox()?;
1934    ///
1935    /// let metadata = NTScalarMetadataBuilder::new()
1936    ///     .alarm(0, 0, "OK")
1937    ///     .display(DisplayMetadata {
1938    ///         limit_low: 0,
1939    ///         limit_high: 100,
1940    ///         description: "Temperature".to_string(),
1941    ///         units: "°C".to_string(),
1942    ///         precision: 2,
1943    ///     })
1944    ///
1945    /// pv.open_double(25.5, metadata)?;
1946    /// # Ok::<(), pvxs_sys::PvxsError>(())
1947    /// ```
1948    pub(crate) fn open_double(
1949        &mut self,
1950        initial_value: f64,
1951        metadata: NTScalarMetadataBuilder,
1952    ) -> Result<()> {
1953        let meta = metadata.build()?;
1954        bridge::shared_pv_open_double(self.inner.pin_mut(), initial_value, &meta)?;
1955        Ok(())
1956    }
1957
1958    /// Open the PV with a double array value and metadata
1959    ///
1960    /// # Arguments
1961    ///
1962    /// * `initial_value` - The initial array value for the PV
1963    /// * `metadata` - Metadata builder for the scalar array PV
1964    pub(crate) fn open_double_array(
1965        &mut self,
1966        initial_value: Vec<f64>,
1967        metadata: NTScalarMetadataBuilder,
1968    ) -> Result<()> {
1969        let meta = metadata.build()?;
1970        bridge::shared_pv_open_double_array(self.inner.pin_mut(), initial_value, &meta)?;
1971        Ok(())
1972    }
1973
1974    /// Open the PV with an enum value and metadata
1975    ///
1976    /// # Arguments
1977    ///
1978    /// * `choices` - List of string choices for the enum
1979    /// * `selected_index` - Initial selected index (0-based)
1980    /// * `metadata` - Metadata builder for the enum PV
1981    pub(crate) fn open_enum(
1982        &mut self,
1983        choices: Vec<&str>,
1984        selected_index: i16,
1985        metadata: NTEnumMetadataBuilder,
1986    ) -> Result<()> {
1987        let meta = metadata.build()?;
1988        let choices_vec: Vec<String> = choices.iter().map(|s| s.to_string()).collect();
1989        bridge::shared_pv_open_enum(self.inner.pin_mut(), choices_vec, selected_index, &meta)?;
1990        Ok(())
1991    }
1992
1993    /// Open the PV with an int32 value and metadata
1994    ///
1995    /// # Arguments
1996    ///
1997    /// * `initial_value` - The initial value for the PV
1998    /// * `metadata` - Metadata builder for the int32 PV
1999    pub(crate) fn open_int32(
2000        &mut self,
2001        initial_value: i32,
2002        metadata: NTScalarMetadataBuilder,
2003    ) -> Result<()> {
2004        let meta = metadata.build()?;
2005        bridge::shared_pv_open_int32(self.inner.pin_mut(), initial_value, &meta)?;
2006        Ok(())
2007    }
2008
2009    /// Open the PV with an int32 array value and metadata
2010    ///
2011    /// # Arguments
2012    ///
2013    /// * `initial_value` - The initial array value for the PV
2014    /// * `metadata` - Metadata builder for the int32 array PV
2015    pub(crate) fn open_int32_array(
2016        &mut self,
2017        initial_value: Vec<i32>,
2018        metadata: NTScalarMetadataBuilder,
2019    ) -> Result<()> {
2020        let meta = metadata.build()?;
2021        bridge::shared_pv_open_int32_array(self.inner.pin_mut(), initial_value, &meta)?;
2022        Ok(())
2023    }
2024
2025    /// Open the PV with a string value and metadata
2026    ///
2027    /// # Arguments
2028    ///
2029    /// * `initial_value` - The initial value for the PV
2030    /// * `metadata` - Metadata builder for the string PV
2031    pub(crate) fn open_string(
2032        &mut self,
2033        initial_value: &str,
2034        metadata: NTScalarMetadataBuilder,
2035    ) -> Result<()> {
2036        let meta = metadata.build()?;
2037        bridge::shared_pv_open_string(self.inner.pin_mut(), initial_value.to_string(), &meta)?;
2038        Ok(())
2039    }
2040
2041    /// Open the PV with a string array value and metadata
2042    ///
2043    /// # Arguments
2044    ///
2045    /// * `initial_value` - The initial array value for the PV
2046    /// * `metadata` - Metadata builder for the string array PV
2047    pub(crate) fn open_string_array(
2048        &mut self,
2049        initial_value: Vec<String>,
2050        metadata: NTScalarMetadataBuilder,
2051    ) -> Result<()> {
2052        let meta = metadata.build()?;
2053        bridge::shared_pv_open_string_array(self.inner.pin_mut(), initial_value, &meta)?;
2054        Ok(())
2055    }
2056
2057    /// Check if the PV is open
2058    pub fn is_open(&self) -> bool {
2059        bridge::shared_pv_is_open(&self.inner)
2060    }
2061
2062    /// Close the PV
2063    pub fn close(&mut self) -> Result<()> {
2064        bridge::shared_pv_close(self.inner.pin_mut())?;
2065        Ok(())
2066    }
2067
2068    /// Post a new double value to the PV
2069    ///
2070    /// This updates the PV value and notifies connected clients.
2071    /// If the PV is a double array, this will just replace the value at position 0.
2072    ///
2073    /// # Arguments
2074    ///
2075    /// * `value` - The new value to post
2076    pub fn post_double(&mut self, value: f64) -> Result<()> {
2077        bridge::shared_pv_post_double(self.inner.pin_mut(), value)?;
2078        Ok(())
2079    }
2080
2081    /// Post a new int32 value to the PV
2082    ///
2083    /// This updates the PV value and notifies connected clients.
2084    /// If the PV is an int32 array, this will just replace the value at position 0.
2085    ///
2086    /// # Arguments
2087    ///
2088    /// * `value` - The new value to post
2089    pub fn post_int32(&mut self, value: i32) -> Result<()> {
2090        bridge::shared_pv_post_int32(self.inner.pin_mut(), value)?;
2091        Ok(())
2092    }
2093
2094    pub(crate) fn post_double_with_alarm(
2095        &mut self,
2096        value: f64,
2097        severity: AlarmSeverity,
2098        status: AlarmStatus,
2099        message: String,
2100    ) -> Result<()> {
2101        bridge::shared_pv_post_double_with_alarm(
2102            self.inner.pin_mut(),
2103            value,
2104            severity as i32,
2105            status as i32,
2106            message,
2107        )?;
2108        Ok(())
2109    }
2110
2111    pub(crate) fn post_int32_with_alarm(
2112        &mut self,
2113        value: i32,
2114        severity: AlarmSeverity,
2115        status: AlarmStatus,
2116        message: String,
2117    ) -> Result<()> {
2118        bridge::shared_pv_post_int32_with_alarm(
2119            self.inner.pin_mut(),
2120            value,
2121            severity as i32,
2122            status as i32,
2123            message,
2124        )?;
2125        Ok(())
2126    }
2127
2128    // TODO: TZ - Review later if needed
2129    /*pub(crate) fn post_enum_with_alarm(&mut self, value: i16, severity: AlarmSeverity, status: AlarmStatus, message: String) -> Result<()> {
2130        bridge::shared_pv_post_enum_with_alarm(self.inner.pin_mut(), value, severity as i32, status as i32, message)?;
2131        Ok(())
2132    }*/
2133
2134    /// Post a new string value to the PV
2135    ///
2136    /// # Arguments
2137    ///
2138    /// * `value` - The new value to post
2139    pub fn post_string(&mut self, value: &str) -> Result<()> {
2140        bridge::shared_pv_post_string(self.inner.pin_mut(), value.to_string())?;
2141        Ok(())
2142    }
2143
2144    /// Post a new enum value to the PV
2145    ///
2146    /// Updates the enum index (value.index field) and notifies connected clients.
2147    ///
2148    /// # Arguments
2149    ///
2150    /// * `value` - The enum index to post (should be valid for the choices array)
2151    pub fn post_enum(&mut self, value: i16) -> Result<()> {
2152        bridge::shared_pv_post_enum(self.inner.pin_mut(), value)?;
2153        Ok(())
2154    }
2155
2156    /// Post a new double array to the PV
2157    ///
2158    /// Updates the array value and notifies connected clients.
2159    ///
2160    /// # Arguments
2161    ///
2162    /// * `value` - The new array to post
2163    pub fn post_double_array(&mut self, value: &[f64]) -> Result<()> {
2164        if value.is_empty() {
2165            return Err(PvxsError::new("Cannot post empty double array"));
2166        }
2167        bridge::shared_pv_post_double_array(self.inner.pin_mut(), value.to_vec())?;
2168        Ok(())
2169    }
2170
2171    /// Post a new int32 array to the PV
2172    ///
2173    /// Updates the array value and notifies connected clients.
2174    ///
2175    /// # Arguments
2176    ///
2177    /// * `value` - The new array to post
2178    pub fn post_int32_array(&mut self, value: &[i32]) -> Result<()> {
2179        if value.is_empty() {
2180            return Err(PvxsError::new("Cannot post empty int32 array"));
2181        }
2182        bridge::shared_pv_post_int32_array(self.inner.pin_mut(), value.to_vec())?;
2183        Ok(())
2184    }
2185
2186    /// Post a new string array to the PV
2187    ///
2188    /// Updates the array value and notifies connected clients.
2189    ///
2190    /// # Arguments
2191    ///
2192    /// * `value` - The new array to post
2193    pub fn post_string_array(&mut self, value: &[String]) -> Result<()> {
2194        if value.is_empty() {
2195            return Err(PvxsError::new("Cannot post empty string array"));
2196        }
2197        bridge::shared_pv_post_string_array(self.inner.pin_mut(), value.to_vec())?;
2198        Ok(())
2199    }
2200
2201    /// Fetch the current value of the PV
2202    ///
2203    /// Returns the current value as a Value that can be inspected.
2204    pub fn fetch(&self) -> Result<Value> {
2205        let inner = bridge::shared_pv_fetch(&self.inner)?;
2206        Ok(Value { inner })
2207    }
2208}
2209
2210/// A static source for organising collections of PVs
2211///
2212/// StaticSource allows grouping related PVs together with common
2213/// configuration and management.
2214///
2215/// # Example
2216///
2217/// ```ignore
2218/// use pvxs_sys::{StaticSource, SharedPV};
2219///
2220/// // Note: This example uses internal APIs
2221/// // Use Server::create_pv_* methods for public API
2222/// let mut source = StaticSource::create()?;
2223///
2224/// let mut temp_pv = SharedPV::create_readonly()?;
2225/// // temp_pv.open_double(23.5)?; // Internal API
2226///
2227/// source.add_pv("temperature", &mut temp_pv)?;
2228///
2229/// // Add source to server with priority 0
2230/// // server.add_source("sensors", &mut source, 0)?;
2231/// # Ok::<(), pvxs_sys::PvxsError>(())
2232/// ```
2233pub struct StaticSource {
2234    inner: UniquePtr<bridge::StaticSourceWrapper>,
2235}
2236
2237impl StaticSource {
2238    /// Create a new StaticSource
2239    pub fn create() -> Result<Self> {
2240        let inner = bridge::static_source_create()?;
2241        Ok(Self { inner })
2242    }
2243
2244    /// Add a PV to this source
2245    ///
2246    /// # Arguments
2247    ///
2248    /// * `name` - The PV name within this source
2249    /// * `pv` - The SharedPV to add
2250    pub fn add_pv(&mut self, name: &str, pv: &mut SharedPV) -> Result<()> {
2251        bridge::static_source_add_pv(self.inner.pin_mut(), name.to_string(), pv.inner.pin_mut())?;
2252        Ok(())
2253    }
2254
2255    /// Remove a PV from this source
2256    ///
2257    /// # Arguments
2258    ///
2259    /// * `name` - The name of the PV to remove
2260    pub fn remove_pv(&mut self, name: &str) -> Result<()> {
2261        bridge::static_source_remove_pv(self.inner.pin_mut(), name.to_string())?;
2262        Ok(())
2263    }
2264
2265    /// Close all PVs in this source
2266    pub fn close_all(&mut self) -> Result<()> {
2267        bridge::static_source_close_all(self.inner.pin_mut())?;
2268        Ok(())
2269    }
2270}
2271
2272// ============================================================================
2273// NTScalar Metadata Support with C++ std::optional
2274// ============================================================================
2275
2276/// Builder for creating NTScalar metadata with optional fields
2277///
2278/// This provides a clean, type-safe API for configuring PV metadata.
2279/// The metadata is constructed using C++ builder functions that support std::optional.
2280///
2281/// ```text
2282/// epics:nt/NTScalar:1.0
2283/// double value
2284/// alarm_t alarm
2285///     int severity
2286///     int status
2287///     string message
2288/// structure timeStamp
2289///     long secondsPastEpoch
2290///     int nanoseconds
2291///     int userTag
2292/// structure display
2293///     double limitLow
2294///     double limitHigh
2295///     string description
2296///     string units
2297///     int precision
2298/// control_t control
2299///     double limitLow
2300///     double limitHigh
2301///     double minStep
2302/// valueAlarm_t valueAlarm
2303///     boolean active
2304///     double lowAlarmLimit
2305///     double lowWarningLimit
2306///     double highWarningLimit
2307///     double highAlarmLimit
2308///     int lowAlarmSeverity
2309///     int lowWarningSeverity
2310///     int highWarningSeverity
2311///     int highAlarmSeverity
2312///     byte hysteresis
2313/// ```
2314#[derive(Debug, Clone)]
2315pub struct NTScalarMetadataBuilder {
2316    alarm_severity: AlarmSeverity,
2317    alarm_status: AlarmStatus,
2318    alarm_message: String,
2319    timestamp_seconds: i64,
2320    timestamp_nanos: i32,
2321    timestamp_user_tag: i32,
2322    display: Option<DisplayMetadata>,
2323    control: Option<ControlMetadata>,
2324    alarm_metadata: Option<AlarmMetadata>,
2325}
2326
2327impl NTScalarMetadataBuilder {
2328    /// Create a new metadata builder with default values
2329    pub fn new() -> Self {
2330        use std::time::{SystemTime, UNIX_EPOCH};
2331        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
2332
2333        Self {
2334            alarm_severity: AlarmSeverity::Invalid,
2335            alarm_status: AlarmStatus::UndefinedStatus,
2336            alarm_message: String::new(),
2337            timestamp_seconds: now.as_secs() as i64,
2338            timestamp_nanos: now.subsec_nanos() as i32,
2339            timestamp_user_tag: 0,
2340            display: None,
2341            control: None,
2342            alarm_metadata: None,
2343        }
2344    }
2345
2346    /// Set alarm information
2347    pub fn alarm(
2348        mut self,
2349        severity: AlarmSeverity,
2350        status: AlarmStatus,
2351        message: impl Into<String>,
2352    ) -> Self {
2353        self.alarm_severity = severity;
2354        self.alarm_status = status;
2355        self.alarm_message = message.into();
2356        self
2357    }
2358
2359    /// Set timestamp (defaults to current time)
2360    pub fn timestamp(mut self, seconds: i64, nanos: i32, user_tag: i32) -> Self {
2361        self.timestamp_seconds = seconds;
2362        self.timestamp_nanos = nanos;
2363        self.timestamp_user_tag = user_tag;
2364        self
2365    }
2366
2367    /// Add display metadata
2368    pub fn display(mut self, meta: DisplayMetadata) -> Self {
2369        self.display = Some(meta);
2370        self
2371    }
2372
2373    /// Add control metadata
2374    pub fn control(mut self, meta: ControlMetadata) -> Self {
2375        self.control = Some(meta);
2376        self
2377    }
2378
2379    /// Add value alarm metadata
2380    pub fn alarm_metadata(mut self, meta: AlarmMetadata) -> Self {
2381        self.alarm_metadata = Some(meta);
2382        self
2383    }
2384
2385    /// Build the metadata using C++ builder functions with std::optional support
2386    fn build(self) -> Result<cxx::UniquePtr<bridge::NTScalarMetadata>> {
2387        // Create alarm and timestamp (always required)
2388        let alarm = bridge::create_alarm(
2389            self.alarm_severity as i32,
2390            self.alarm_status as i32,
2391            self.alarm_message,
2392        );
2393        let time_stamp = bridge::create_time(
2394            self.timestamp_seconds,
2395            self.timestamp_nanos,
2396            self.timestamp_user_tag,
2397        );
2398
2399        let make_display = |d: &DisplayMetadata| {
2400            bridge::create_display(
2401                d.limit_low,
2402                d.limit_high,
2403                d.description.clone(),
2404                d.units.clone(),
2405                d.precision,
2406            )
2407        };
2408
2409        // Build metadata based on which optional fields are present
2410        let metadata = match (&self.display, &self.control, &self.alarm_metadata) {
2411            (None, None, None) => bridge::create_metadata_no_optional(&alarm, &time_stamp),
2412            (Some(d), None, None) => {
2413                let display = make_display(d);
2414                bridge::create_metadata_with_display(&alarm, &time_stamp, &display)
2415            }
2416            (None, Some(c), None) => {
2417                let control = bridge::create_control(c.limit_low, c.limit_high, c.min_step);
2418                bridge::create_metadata_with_control(&alarm, &time_stamp, &control)
2419            }
2420            (None, None, Some(v)) => {
2421                let value_alarm = bridge::create_value_alarm(
2422                    v.active,
2423                    v.low_alarm_limit,
2424                    v.low_warning_limit,
2425                    v.high_warning_limit,
2426                    v.high_alarm_limit,
2427                    v.low_alarm_severity as i32,
2428                    v.low_warning_severity as i32,
2429                    v.high_warning_severity as i32,
2430                    v.high_alarm_severity as i32,
2431                    v.hysteresis,
2432                );
2433                bridge::create_metadata_with_value_alarm(&alarm, &time_stamp, &value_alarm)
2434            }
2435            (Some(d), Some(c), None) => {
2436                let display = make_display(d);
2437                let control = bridge::create_control(c.limit_low, c.limit_high, c.min_step);
2438                bridge::create_metadata_with_display_control(
2439                    &alarm,
2440                    &time_stamp,
2441                    &display,
2442                    &control,
2443                )
2444            }
2445            (Some(d), None, Some(v)) => {
2446                let display = make_display(d);
2447                let value_alarm = bridge::create_value_alarm(
2448                    v.active,
2449                    v.low_alarm_limit,
2450                    v.low_warning_limit,
2451                    v.high_warning_limit,
2452                    v.high_alarm_limit,
2453                    v.low_alarm_severity as i32,
2454                    v.low_warning_severity as i32,
2455                    v.high_warning_severity as i32,
2456                    v.high_alarm_severity as i32,
2457                    v.hysteresis,
2458                );
2459                bridge::create_metadata_with_display_value_alarm(
2460                    &alarm,
2461                    &time_stamp,
2462                    &display,
2463                    &value_alarm,
2464                )
2465            }
2466            (None, Some(c), Some(v)) => {
2467                let control = bridge::create_control(c.limit_low, c.limit_high, c.min_step);
2468                let value_alarm = bridge::create_value_alarm(
2469                    v.active,
2470                    v.low_alarm_limit,
2471                    v.low_warning_limit,
2472                    v.high_warning_limit,
2473                    v.high_alarm_limit,
2474                    v.low_alarm_severity as i32,
2475                    v.low_warning_severity as i32,
2476                    v.high_warning_severity as i32,
2477                    v.high_alarm_severity as i32,
2478                    v.hysteresis,
2479                );
2480                bridge::create_metadata_with_control_value_alarm(
2481                    &alarm,
2482                    &time_stamp,
2483                    &control,
2484                    &value_alarm,
2485                )
2486            }
2487            (Some(d), Some(c), Some(v)) => {
2488                let display = make_display(d);
2489                let control = bridge::create_control(c.limit_low, c.limit_high, c.min_step);
2490                let value_alarm = bridge::create_value_alarm(
2491                    v.active,
2492                    v.low_alarm_limit,
2493                    v.low_warning_limit,
2494                    v.high_warning_limit,
2495                    v.high_alarm_limit,
2496                    v.low_alarm_severity as i32,
2497                    v.low_warning_severity as i32,
2498                    v.high_warning_severity as i32,
2499                    v.high_alarm_severity as i32,
2500                    v.hysteresis,
2501                );
2502                bridge::create_metadata_full(&alarm, &time_stamp, &display, &control, &value_alarm)
2503            }
2504        };
2505
2506        Ok(metadata)
2507    }
2508}
2509
2510impl Default for NTScalarMetadataBuilder {
2511    fn default() -> Self {
2512        Self::new()
2513    }
2514}
2515
2516// ============================================================================
2517// NTEnum Metadata support
2518// ============================================================================
2519/// Builder for creating NTEnum metadata
2520///
2521/// This provides a clean, type-safe API for configuring enum PV metadata.
2522/// The metadata is constructed using C++ builder functions.
2523///
2524/// ```text
2525/// epics:nt/NTEnum:1.0
2526/// enum_t value
2527///     int index
2528///     string[] choices
2529/// alarm_t alarm
2530///     int severity
2531///     int status
2532///     string message
2533/// structure timeStamp
2534///     long secondsPastEpoch
2535///     int nanoseconds
2536///     int userTag
2537/// ```
2538pub struct NTEnumMetadataBuilder {
2539    alarm_severity: i32,
2540    alarm_status: i32,
2541    alarm_message: String,
2542    timestamp_seconds: i64,
2543    timestamp_nanos: i32,
2544    timestamp_user_tag: i32,
2545}
2546
2547impl NTEnumMetadataBuilder {
2548    /// Create a new metadata builder with default values
2549    pub fn new() -> Self {
2550        use std::time::{SystemTime, UNIX_EPOCH};
2551        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
2552
2553        Self {
2554            alarm_severity: 0,
2555            alarm_status: 0,
2556            alarm_message: String::new(),
2557            timestamp_seconds: now.as_secs() as i64,
2558            timestamp_nanos: now.subsec_nanos() as i32,
2559            timestamp_user_tag: 0,
2560        }
2561    }
2562
2563    /// Set alarm information
2564    pub fn alarm(mut self, severity: i32, status: i32, message: impl Into<String>) -> Self {
2565        self.alarm_severity = severity;
2566        self.alarm_status = status;
2567        self.alarm_message = message.into();
2568        self
2569    }
2570
2571    /// Set timestamp (defaults to current time)
2572    pub fn timestamp(mut self, seconds: i64, nanos: i32, user_tag: i32) -> Self {
2573        self.timestamp_seconds = seconds;
2574        self.timestamp_nanos = nanos;
2575        self.timestamp_user_tag = user_tag;
2576        self
2577    }
2578
2579    fn build(self) -> Result<cxx::UniquePtr<bridge::NTEnumMetadata>> {
2580        let alarm =
2581            bridge::create_alarm(self.alarm_severity, self.alarm_status, self.alarm_message);
2582        let time_stamp = bridge::create_time(
2583            self.timestamp_seconds,
2584            self.timestamp_nanos,
2585            self.timestamp_user_tag,
2586        );
2587        let metadata = bridge::create_enum_metadata(&alarm, &time_stamp);
2588        Ok(metadata)
2589    }
2590}
2591
2592impl Default for NTEnumMetadataBuilder {
2593    fn default() -> Self {
2594        Self::new()
2595    }
2596}