Skip to main content

asyn_rs/
port.rs

1//! Port driver base and trait.
2//!
3//! # I/O Model
4//!
5//! Ports are driven by a `PortActor` running on a dedicated thread.
6//! The actor exclusively owns the driver and processes requests from a channel.
7//!
8//! **Cache path** (default `read_*`/`write_*` methods):
9//! - Default implementations operate on the parameter cache (non-blocking).
10//! - Background tasks update cache via `set_*_param()` + `call_param_callbacks()`.
11//!
12//! **Actor path** (requests submitted via [`crate::port_handle::PortHandle`]):
13//! - Each port gets a dedicated actor thread that dispatches requests to driver methods.
14//! - `can_block` indicates the port may perform blocking I/O.
15
16use std::collections::HashMap;
17use std::sync::Arc;
18use std::time::SystemTime;
19
20use std::any::Any;
21
22/// Per-address device state for multi-device ports.
23#[derive(Debug, Clone)]
24pub struct DeviceState {
25    pub connected: bool,
26    pub enabled: bool,
27    pub auto_connect: bool,
28}
29
30impl Default for DeviceState {
31    fn default() -> Self {
32        Self {
33            connected: true,
34            enabled: true,
35            auto_connect: true,
36        }
37    }
38}
39
40use crate::error::{AsynError, AsynResult, AsynStatus};
41use crate::exception::{AsynException, ExceptionEvent, ExceptionManager};
42use crate::interpose::{OctetInterpose, OctetInterposeStack};
43use crate::interrupt::{InterruptManager, InterruptValue};
44use crate::param::{EnumEntry, ParamList, ParamType};
45use crate::trace::TraceManager;
46use crate::user::AsynUser;
47
48/// C asyn `queueRequest` priority. In asyn-rs this exists as compatibility
49/// metadata only — there is no actual request queue or priority-based scheduling.
50/// Drivers manage their own async tasks directly.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
52pub enum QueuePriority {
53    Low = 0,
54    #[default]
55    Medium = 1,
56    High = 2,
57    /// Connect/disconnect operations — processed even when disabled/disconnected.
58    Connect = 3,
59}
60
61/// Port configuration flags.
62#[derive(Debug, Clone, Copy)]
63pub struct PortFlags {
64    /// True if port supports multiple sub-addresses (ASYN_MULTIDEVICE).
65    pub multi_device: bool,
66    /// True if port can block (ASYN_CANBLOCK).
67    ///
68    /// When `true`, the port gets a dedicated worker thread that serializes I/O via a
69    /// priority queue (matching C asyn's per-port thread model).
70    ///
71    /// When `false`, requests execute synchronously inline on the caller's thread
72    /// (no worker thread is spawned). This is appropriate for non-blocking drivers
73    /// whose `io_*` methods return immediately (e.g., cache-based parameter access).
74    pub can_block: bool,
75    /// True if port can be destroyed via shutdown_port (ASYN_DESTRUCTIBLE).
76    pub destructible: bool,
77}
78
79impl Default for PortFlags {
80    fn default() -> Self {
81        Self {
82            multi_device: false,
83            can_block: false,
84            destructible: true,
85        }
86    }
87}
88
89/// Base state shared by all port drivers.
90/// Contains the parameter library, interrupt manager, and connection state.
91///
92/// # Interpose concurrency
93///
94/// `interpose_octet` requires `&mut self` for all operations (both `push` and
95/// `dispatch_*`). Since `PortDriverBase` is always behind `Arc<Mutex<dyn PortDriver>>`,
96/// any access to `interpose_octet` requires the port lock. This naturally
97/// serializes interpose modifications with I/O dispatch — no additional
98/// synchronization is needed. **Callers must never modify the interpose stack
99/// without holding the port lock.**
100pub struct PortDriverBase {
101    pub port_name: String,
102    pub max_addr: usize,
103    pub flags: PortFlags,
104    pub params: ParamList,
105    pub interrupts: InterruptManager,
106    pub connected: bool,
107    pub enabled: bool,
108    pub auto_connect: bool,
109    /// Exception sink injected by [`crate::manager::PortManager`] on registration.
110    pub exception_sink: Option<Arc<ExceptionManager>>,
111    pub options: HashMap<String, String>,
112    /// Input EOS sequence (max 2 bytes). Used by EOS interpose and drivers.
113    pub input_eos: Vec<u8>,
114    /// Output EOS sequence (max 2 bytes). Used by EOS interpose and drivers.
115    pub output_eos: Vec<u8>,
116    pub interpose_octet: OctetInterposeStack,
117    pub trace: Option<Arc<TraceManager>>,
118    /// Per-address device state for multi-device ports.
119    pub device_states: HashMap<i32, DeviceState>,
120    /// Timestamp source callback for custom timestamps.
121    pub timestamp_source: Option<Arc<dyn Fn() -> SystemTime + Send + Sync>>,
122}
123
124impl PortDriverBase {
125    pub fn new(port_name: &str, max_addr: usize, flags: PortFlags) -> Self {
126        Self {
127            port_name: port_name.to_string(),
128            max_addr: max_addr.max(1),
129            flags,
130            params: ParamList::new(max_addr, flags.multi_device),
131            interrupts: InterruptManager::new(256),
132            connected: true,
133            enabled: true,
134            auto_connect: true,
135            exception_sink: None,
136            options: HashMap::new(),
137            input_eos: Vec::new(),
138            output_eos: Vec::new(),
139            interpose_octet: OctetInterposeStack::new(),
140            trace: None,
141            device_states: HashMap::new(),
142            timestamp_source: None,
143        }
144    }
145
146    /// Announce an exception through the global exception manager (if injected).
147    pub fn announce_exception(&self, exception: AsynException, addr: i32) {
148        if let Some(ref sink) = self.exception_sink {
149            sink.announce(&ExceptionEvent {
150                port_name: self.port_name.clone(),
151                exception,
152                addr,
153            });
154        }
155    }
156
157    /// Query whether the port is connected.
158    pub fn is_connected(&self) -> bool {
159        self.connected
160    }
161
162    /// Query whether the port is enabled.
163    pub fn is_enabled(&self) -> bool {
164        self.enabled
165    }
166
167    /// Query whether auto-connect is enabled.
168    pub fn is_auto_connect(&self) -> bool {
169        self.auto_connect
170    }
171
172    /// Check that the port is both enabled and connected.
173    /// Returns `Err(Disabled)` or `Err(Disconnected)` otherwise.
174    pub fn check_ready(&self) -> AsynResult<()> {
175        if !self.enabled {
176            return Err(AsynError::Status {
177                status: AsynStatus::Disabled,
178                message: format!("port {} is disabled", self.port_name),
179            });
180        }
181        if !self.connected {
182            return Err(AsynError::Status {
183                status: AsynStatus::Disconnected,
184                message: format!("port {} is disconnected", self.port_name),
185            });
186        }
187        Ok(())
188    }
189
190    /// Check that port + device address are both ready.
191    /// For multi-device ports, checks per-address state in addition to port-level state.
192    pub fn check_ready_addr(&self, addr: i32) -> AsynResult<()> {
193        self.check_ready()?;
194        if self.flags.multi_device {
195            if let Some(ds) = self.device_states.get(&addr) {
196                if !ds.enabled {
197                    return Err(AsynError::Status {
198                        status: AsynStatus::Disabled,
199                        message: format!("port {} addr {} is disabled", self.port_name, addr),
200                    });
201                }
202                if !ds.connected {
203                    return Err(AsynError::Status {
204                        status: AsynStatus::Disconnected,
205                        message: format!("port {} addr {} is disconnected", self.port_name, addr),
206                    });
207                }
208            }
209        }
210        Ok(())
211    }
212
213    /// Get or create a device state for the given address.
214    pub fn device_state(&mut self, addr: i32) -> &mut DeviceState {
215        self.device_states.entry(addr).or_default()
216    }
217
218    /// Check if a specific device address is connected.
219    pub fn is_device_connected(&self, addr: i32) -> bool {
220        self.device_states
221            .get(&addr)
222            .map_or(true, |ds| ds.connected)
223    }
224
225    /// Set a specific device address as connected.
226    pub fn connect_addr(&mut self, addr: i32) {
227        self.device_state(addr).connected = true;
228        self.announce_exception(AsynException::Connect, addr);
229    }
230
231    /// Set a specific device address as disconnected.
232    pub fn disconnect_addr(&mut self, addr: i32) {
233        self.device_state(addr).connected = false;
234        self.announce_exception(AsynException::Connect, addr);
235    }
236
237    /// Enable a specific device address.
238    pub fn enable_addr(&mut self, addr: i32) {
239        self.device_state(addr).enabled = true;
240        self.announce_exception(AsynException::Enable, addr);
241    }
242
243    /// Disable a specific device address.
244    pub fn disable_addr(&mut self, addr: i32) {
245        self.device_state(addr).enabled = false;
246        self.announce_exception(AsynException::Enable, addr);
247    }
248
249    /// Set a custom timestamp source callback.
250    pub fn register_timestamp_source<F>(&mut self, source: F)
251    where
252        F: Fn() -> SystemTime + Send + Sync + 'static,
253    {
254        self.timestamp_source = Some(Arc::new(source));
255    }
256
257    /// Get current timestamp from the registered source, or SystemTime::now().
258    pub fn current_timestamp(&self) -> SystemTime {
259        self.timestamp_source
260            .as_ref()
261            .map_or_else(SystemTime::now, |f| f())
262    }
263
264    pub fn create_param(&mut self, name: &str, param_type: ParamType) -> AsynResult<usize> {
265        self.params.create_param(name, param_type)
266    }
267
268    pub fn find_param(&self, name: &str) -> Option<usize> {
269        self.params.find_param(name)
270    }
271
272    // --- Convenience param accessors ---
273
274    pub fn set_int32_param(&mut self, index: usize, addr: i32, value: i32) -> AsynResult<()> {
275        self.params.set_int32(index, addr, value)
276    }
277
278    pub fn get_int32_param(&self, index: usize, addr: i32) -> AsynResult<i32> {
279        self.params.get_int32(index, addr)
280    }
281
282    pub fn set_int64_param(&mut self, index: usize, addr: i32, value: i64) -> AsynResult<()> {
283        self.params.set_int64(index, addr, value)
284    }
285
286    pub fn get_int64_param(&self, index: usize, addr: i32) -> AsynResult<i64> {
287        self.params.get_int64(index, addr)
288    }
289
290    pub fn set_float64_param(&mut self, index: usize, addr: i32, value: f64) -> AsynResult<()> {
291        self.params.set_float64(index, addr, value)
292    }
293
294    pub fn get_float64_param(&self, index: usize, addr: i32) -> AsynResult<f64> {
295        self.params.get_float64(index, addr)
296    }
297
298    pub fn set_string_param(&mut self, index: usize, addr: i32, value: String) -> AsynResult<()> {
299        self.params.set_string(index, addr, value)
300    }
301
302    pub fn get_string_param(&self, index: usize, addr: i32) -> AsynResult<&str> {
303        self.params.get_string(index, addr)
304    }
305
306    pub fn set_uint32_param(
307        &mut self,
308        index: usize,
309        addr: i32,
310        value: u32,
311        mask: u32,
312    ) -> AsynResult<()> {
313        self.params.set_uint32(index, addr, value, mask)
314    }
315
316    pub fn get_uint32_param(&self, index: usize, addr: i32) -> AsynResult<u32> {
317        self.params.get_uint32(index, addr)
318    }
319
320    pub fn get_enum_param(&self, index: usize, addr: i32) -> AsynResult<(usize, Arc<[EnumEntry]>)> {
321        self.params.get_enum(index, addr)
322    }
323
324    pub fn set_enum_index_param(
325        &mut self,
326        index: usize,
327        addr: i32,
328        value: usize,
329    ) -> AsynResult<()> {
330        self.params.set_enum_index(index, addr, value)
331    }
332
333    pub fn set_enum_choices_param(
334        &mut self,
335        index: usize,
336        addr: i32,
337        choices: Arc<[EnumEntry]>,
338    ) -> AsynResult<()> {
339        self.params.set_enum_choices(index, addr, choices)
340    }
341
342    pub fn get_generic_pointer_param(
343        &self,
344        index: usize,
345        addr: i32,
346    ) -> AsynResult<Arc<dyn Any + Send + Sync>> {
347        self.params.get_generic_pointer(index, addr)
348    }
349
350    pub fn set_generic_pointer_param(
351        &mut self,
352        index: usize,
353        addr: i32,
354        value: Arc<dyn Any + Send + Sync>,
355    ) -> AsynResult<()> {
356        self.params.set_generic_pointer(index, addr, value)
357    }
358
359    pub fn set_param_timestamp(
360        &mut self,
361        index: usize,
362        addr: i32,
363        ts: SystemTime,
364    ) -> AsynResult<()> {
365        self.params.set_timestamp(index, addr, ts)
366    }
367
368    pub fn set_param_status(
369        &mut self,
370        index: usize,
371        addr: i32,
372        status: AsynStatus,
373        alarm_status: u16,
374        alarm_severity: u16,
375    ) -> AsynResult<()> {
376        self.params
377            .set_param_status(index, addr, status, alarm_status, alarm_severity)
378    }
379
380    pub fn get_param_status(&self, index: usize, addr: i32) -> AsynResult<(AsynStatus, u16, u16)> {
381        self.params.get_param_status(index, addr)
382    }
383
384    /// Detailed parameter report matching C asynPortDriver::reportParams.
385    pub fn report_params(&self, level: i32) {
386        eprintln!("  Number of parameters is {}", self.params.len());
387        if level < 1 {
388            return;
389        }
390        for i in 0..self.params.len() {
391            let name = self.params.param_name(i).unwrap_or("?");
392            let ptype = self
393                .params
394                .param_type(i)
395                .map(|t| format!("{t:?}"))
396                .unwrap_or("?".into());
397            if level >= 2 {
398                for addr in 0..self.max_addr.max(1) {
399                    let val = self
400                        .params
401                        .get_value(i, addr as i32)
402                        .map(|v| format!("{v:?}"))
403                        .unwrap_or("undefined".into());
404                    let (status, alarm_st, alarm_sev) = self
405                        .params
406                        .get_param_status(i, addr as i32)
407                        .unwrap_or((AsynStatus::Success, 0, 0));
408                    eprintln!(
409                        "  param[{i}] name={name} type={ptype} addr={addr} val={val} status={status:?} alarm=({alarm_st},{alarm_sev})"
410                    );
411                }
412            } else {
413                eprintln!("  param[{i}] name={name} type={ptype}");
414            }
415        }
416    }
417
418    /// Push an interpose layer onto the octet I/O stack.
419    ///
420    /// **Concurrency**: requires `&mut self`, which means the caller must hold
421    /// the port lock (`Arc<Mutex<dyn PortDriver>>`). This ensures
422    /// interpose modifications are serialized with I/O dispatch.
423    pub fn push_octet_interpose(&mut self, layer: Box<dyn OctetInterpose>) {
424        self.interpose_octet.push(layer);
425    }
426
427    /// Flush changed parameters as interrupt notifications.
428    /// Equivalent to C asyn's callParamCallbacks().
429    pub fn call_param_callbacks(&mut self, addr: i32) -> AsynResult<()> {
430        let changed = self.params.take_changed(addr)?;
431        let now = self.current_timestamp();
432        for reason in changed {
433            let value = self.params.get_value(reason, addr)?.clone();
434            let ts = self.params.get_timestamp(reason, addr)?.unwrap_or(now);
435            let uint32_mask = self
436                .params
437                .get_uint32_interrupt_mask(reason, addr)
438                .unwrap_or(0);
439            self.interrupts.notify(InterruptValue {
440                reason,
441                addr,
442                value,
443                timestamp: ts,
444                uint32_changed_mask: uint32_mask,
445            });
446        }
447        Ok(())
448    }
449
450    /// Flush a single parameter's changed flag and notify if dirty.
451    /// Use this instead of `call_param_callbacks` when you want to avoid
452    /// flushing unrelated parameters (e.g. rapidly-updating CP-linked params).
453    pub fn call_param_callback(&mut self, addr: i32, reason: usize) -> AsynResult<()> {
454        if self.params.take_changed_single(reason, addr)? {
455            let now = self.current_timestamp();
456            let value = self.params.get_value(reason, addr)?.clone();
457            let ts = self.params.get_timestamp(reason, addr)?.unwrap_or(now);
458            let uint32_mask = self
459                .params
460                .get_uint32_interrupt_mask(reason, addr)
461                .unwrap_or(0);
462            self.interrupts.notify(InterruptValue {
463                reason,
464                addr,
465                value,
466                timestamp: ts,
467                uint32_changed_mask: uint32_mask,
468            });
469        }
470        Ok(())
471    }
472
473    /// Mark a parameter as changed without modifying its value.
474    ///
475    /// Use this to trigger I/O Intr on params whose data is served via
476    /// `read_*_array()` overrides rather than the param cache (e.g. pixel data).
477    pub fn mark_param_changed(&mut self, index: usize, addr: i32) -> AsynResult<()> {
478        self.params.mark_changed(index, addr)
479    }
480}
481
482/// Port driver trait. All methods have default implementations that operate
483/// on the parameter cache (no actual I/O).
484///
485/// Drivers performing real hardware I/O should:
486/// 1. Run I/O in a background task (e.g., tokio::spawn)
487/// 2. Update parameters via `base_mut().set_*_param()` + `call_param_callbacks()`
488/// 3. Let the default `read_*` methods return cached values
489///
490/// # LockPort/UnlockPort
491///
492/// C asyn provides `lockPort`/`unlockPort` for direct mutex locking. In asyn-rs,
493/// the port is always behind `Arc<Mutex<dyn PortDriver>>`, so callers hold the
494/// parking_lot mutex directly. For multi-request exclusive access, use
495/// `BlockProcess`/`UnblockProcess` via the worker queue.
496pub trait PortDriver: Send + Sync + 'static {
497    fn base(&self) -> &PortDriverBase;
498    fn base_mut(&mut self) -> &mut PortDriverBase;
499
500    // --- AsynCommon ---
501
502    fn connect(&mut self, _user: &AsynUser) -> AsynResult<()> {
503        self.base_mut().connected = true;
504        self.base().announce_exception(AsynException::Connect, -1);
505        Ok(())
506    }
507
508    fn disconnect(&mut self, _user: &AsynUser) -> AsynResult<()> {
509        self.base_mut().connected = false;
510        self.base().announce_exception(AsynException::Connect, -1);
511        Ok(())
512    }
513
514    fn enable(&mut self, _user: &AsynUser) -> AsynResult<()> {
515        self.base_mut().enabled = true;
516        self.base().announce_exception(AsynException::Enable, -1);
517        Ok(())
518    }
519
520    fn disable(&mut self, _user: &AsynUser) -> AsynResult<()> {
521        self.base_mut().enabled = false;
522        self.base().announce_exception(AsynException::Enable, -1);
523        Ok(())
524    }
525
526    fn connect_addr(&mut self, user: &AsynUser) -> AsynResult<()> {
527        self.base_mut().connect_addr(user.addr);
528        Ok(())
529    }
530
531    fn disconnect_addr(&mut self, user: &AsynUser) -> AsynResult<()> {
532        self.base_mut().disconnect_addr(user.addr);
533        Ok(())
534    }
535
536    fn enable_addr(&mut self, user: &AsynUser) -> AsynResult<()> {
537        self.base_mut().enable_addr(user.addr);
538        Ok(())
539    }
540
541    fn disable_addr(&mut self, user: &AsynUser) -> AsynResult<()> {
542        self.base_mut().disable_addr(user.addr);
543        Ok(())
544    }
545
546    fn get_option(&self, key: &str) -> AsynResult<String> {
547        self.base()
548            .options
549            .get(key)
550            .cloned()
551            .ok_or_else(|| AsynError::OptionNotFound(key.to_string()))
552    }
553
554    fn set_option(&mut self, key: &str, value: &str) -> AsynResult<()> {
555        self.base_mut()
556            .options
557            .insert(key.to_string(), value.to_string());
558        Ok(())
559    }
560
561    fn report(&self, level: i32) {
562        let base = self.base();
563        eprintln!("Port: {}", base.port_name);
564        eprintln!(
565            "  connected: {}, max_addr: {}, params: {}, options: {}",
566            base.connected,
567            base.max_addr,
568            base.params.len(),
569            base.options.len()
570        );
571        if level >= 1 {
572            base.report_params(level.saturating_sub(1));
573        }
574        if level >= 2 {
575            for (k, v) in &base.options {
576                eprintln!("  option: {k} = {v}");
577            }
578        }
579    }
580
581    // --- Scalar I/O (cache-based defaults, timeout not applicable) ---
582
583    // Cache-based defaults do NOT check connection state (C parity).
584    // The port actor checks check_ready_addr() before dispatching, matching
585    // C asyn where asynManager checks connection before calling the driver.
586
587    fn read_int32(&mut self, user: &AsynUser) -> AsynResult<i32> {
588        self.base().params.get_int32(user.reason, user.addr)
589    }
590
591    fn write_int32(&mut self, user: &mut AsynUser, value: i32) -> AsynResult<()> {
592        self.base_mut()
593            .params
594            .set_int32(user.reason, user.addr, value)?;
595        self.base_mut().call_param_callbacks(user.addr)
596    }
597
598    fn read_int64(&mut self, user: &AsynUser) -> AsynResult<i64> {
599        self.base().params.get_int64(user.reason, user.addr)
600    }
601
602    fn write_int64(&mut self, user: &mut AsynUser, value: i64) -> AsynResult<()> {
603        self.base_mut()
604            .params
605            .set_int64(user.reason, user.addr, value)?;
606        self.base_mut().call_param_callbacks(user.addr)
607    }
608
609    fn get_bounds_int32(&self, _user: &AsynUser) -> AsynResult<(i32, i32)> {
610        Ok((i32::MIN, i32::MAX))
611    }
612
613    fn get_bounds_int64(&self, _user: &AsynUser) -> AsynResult<(i64, i64)> {
614        Ok((i64::MIN, i64::MAX))
615    }
616
617    fn read_float64(&mut self, user: &AsynUser) -> AsynResult<f64> {
618        self.base().params.get_float64(user.reason, user.addr)
619    }
620
621    fn write_float64(&mut self, user: &mut AsynUser, value: f64) -> AsynResult<()> {
622        self.base_mut()
623            .params
624            .set_float64(user.reason, user.addr, value)?;
625        self.base_mut().call_param_callbacks(user.addr)
626    }
627
628    fn read_octet(&mut self, user: &AsynUser, buf: &mut [u8]) -> AsynResult<usize> {
629        let s = self.base().params.get_string(user.reason, user.addr)?;
630        let bytes = s.as_bytes();
631        let n = bytes.len().min(buf.len());
632        buf[..n].copy_from_slice(&bytes[..n]);
633        Ok(n)
634    }
635
636    fn write_octet(&mut self, user: &mut AsynUser, data: &[u8]) -> AsynResult<()> {
637        let s = String::from_utf8_lossy(data).into_owned();
638        self.base_mut()
639            .params
640            .set_string(user.reason, user.addr, s)?;
641        self.base_mut().call_param_callbacks(user.addr)
642    }
643
644    fn read_uint32_digital(&mut self, user: &AsynUser, mask: u32) -> AsynResult<u32> {
645        let val = self.base().params.get_uint32(user.reason, user.addr)?;
646        Ok(val & mask)
647    }
648
649    fn write_uint32_digital(
650        &mut self,
651        user: &mut AsynUser,
652        value: u32,
653        mask: u32,
654    ) -> AsynResult<()> {
655        self.base_mut()
656            .params
657            .set_uint32(user.reason, user.addr, value, mask)?;
658        self.base_mut().call_param_callbacks(user.addr)
659    }
660
661    // --- Enum I/O (cache-based defaults) ---
662
663    fn read_enum(&mut self, user: &AsynUser) -> AsynResult<(usize, Arc<[EnumEntry]>)> {
664        self.base().params.get_enum(user.reason, user.addr)
665    }
666
667    fn write_enum(&mut self, user: &mut AsynUser, index: usize) -> AsynResult<()> {
668        self.base_mut()
669            .params
670            .set_enum_index(user.reason, user.addr, index)?;
671        self.base_mut().call_param_callbacks(user.addr)
672    }
673
674    fn write_enum_choices(
675        &mut self,
676        user: &mut AsynUser,
677        choices: Arc<[EnumEntry]>,
678    ) -> AsynResult<()> {
679        self.base_mut()
680            .params
681            .set_enum_choices(user.reason, user.addr, choices)?;
682        self.base_mut().call_param_callbacks(user.addr)
683    }
684
685    // --- GenericPointer I/O (cache-based defaults) ---
686
687    fn read_generic_pointer(&mut self, user: &AsynUser) -> AsynResult<Arc<dyn Any + Send + Sync>> {
688        self.base()
689            .params
690            .get_generic_pointer(user.reason, user.addr)
691    }
692
693    fn write_generic_pointer(
694        &mut self,
695        user: &mut AsynUser,
696        value: Arc<dyn Any + Send + Sync>,
697    ) -> AsynResult<()> {
698        self.base_mut()
699            .params
700            .set_generic_pointer(user.reason, user.addr, value)?;
701        self.base_mut().call_param_callbacks(user.addr)
702    }
703
704    // --- Array I/O (default: not supported) ---
705
706    fn read_float64_array(&mut self, _user: &AsynUser, _buf: &mut [f64]) -> AsynResult<usize> {
707        Err(AsynError::InterfaceNotSupported("asynFloat64Array".into()))
708    }
709
710    fn write_float64_array(&mut self, user: &AsynUser, data: &[f64]) -> AsynResult<()> {
711        self.base_mut()
712            .params
713            .set_float64_array(user.reason, user.addr, data.to_vec())?;
714        self.base_mut().call_param_callbacks(user.addr)
715    }
716
717    fn read_int32_array(&mut self, _user: &AsynUser, _buf: &mut [i32]) -> AsynResult<usize> {
718        Err(AsynError::InterfaceNotSupported("asynInt32Array".into()))
719    }
720
721    fn write_int32_array(&mut self, user: &AsynUser, data: &[i32]) -> AsynResult<()> {
722        self.base_mut()
723            .params
724            .set_int32_array(user.reason, user.addr, data.to_vec())?;
725        self.base_mut().call_param_callbacks(user.addr)
726    }
727
728    fn read_int8_array(&mut self, _user: &AsynUser, _buf: &mut [i8]) -> AsynResult<usize> {
729        Err(AsynError::InterfaceNotSupported("asynInt8Array".into()))
730    }
731
732    fn write_int8_array(&mut self, user: &AsynUser, data: &[i8]) -> AsynResult<()> {
733        self.base_mut()
734            .params
735            .set_int8_array(user.reason, user.addr, data.to_vec())?;
736        self.base_mut().call_param_callbacks(user.addr)
737    }
738
739    fn read_int16_array(&mut self, _user: &AsynUser, _buf: &mut [i16]) -> AsynResult<usize> {
740        Err(AsynError::InterfaceNotSupported("asynInt16Array".into()))
741    }
742
743    fn write_int16_array(&mut self, user: &AsynUser, data: &[i16]) -> AsynResult<()> {
744        self.base_mut()
745            .params
746            .set_int16_array(user.reason, user.addr, data.to_vec())?;
747        self.base_mut().call_param_callbacks(user.addr)
748    }
749
750    fn read_int64_array(&mut self, _user: &AsynUser, _buf: &mut [i64]) -> AsynResult<usize> {
751        Err(AsynError::InterfaceNotSupported("asynInt64Array".into()))
752    }
753
754    fn write_int64_array(&mut self, user: &AsynUser, data: &[i64]) -> AsynResult<()> {
755        self.base_mut()
756            .params
757            .set_int64_array(user.reason, user.addr, data.to_vec())?;
758        self.base_mut().call_param_callbacks(user.addr)
759    }
760
761    fn read_float32_array(&mut self, _user: &AsynUser, _buf: &mut [f32]) -> AsynResult<usize> {
762        Err(AsynError::InterfaceNotSupported("asynFloat32Array".into()))
763    }
764
765    fn write_float32_array(&mut self, user: &AsynUser, data: &[f32]) -> AsynResult<()> {
766        self.base_mut()
767            .params
768            .set_float32_array(user.reason, user.addr, data.to_vec())?;
769        self.base_mut().call_param_callbacks(user.addr)
770    }
771
772    // --- I/O methods (worker thread calls these) ---
773    // Default: delegate to cache-based read_*/write_* for backward compat.
774    // Real I/O drivers override these for actual hardware access.
775
776    fn io_read_octet(&mut self, user: &AsynUser, buf: &mut [u8]) -> AsynResult<usize> {
777        self.read_octet(user, buf)
778    }
779
780    fn io_write_octet(&mut self, user: &mut AsynUser, data: &[u8]) -> AsynResult<()> {
781        self.write_octet(user, data)
782    }
783
784    fn io_read_int32(&mut self, user: &AsynUser) -> AsynResult<i32> {
785        self.read_int32(user)
786    }
787
788    fn io_write_int32(&mut self, user: &mut AsynUser, value: i32) -> AsynResult<()> {
789        self.write_int32(user, value)
790    }
791
792    fn io_read_int64(&mut self, user: &AsynUser) -> AsynResult<i64> {
793        self.read_int64(user)
794    }
795
796    fn io_write_int64(&mut self, user: &mut AsynUser, value: i64) -> AsynResult<()> {
797        self.write_int64(user, value)
798    }
799
800    fn io_read_float64(&mut self, user: &AsynUser) -> AsynResult<f64> {
801        self.read_float64(user)
802    }
803
804    fn io_write_float64(&mut self, user: &mut AsynUser, value: f64) -> AsynResult<()> {
805        self.write_float64(user, value)
806    }
807
808    fn io_read_uint32_digital(&mut self, user: &AsynUser, mask: u32) -> AsynResult<u32> {
809        self.read_uint32_digital(user, mask)
810    }
811
812    fn io_write_uint32_digital(
813        &mut self,
814        user: &mut AsynUser,
815        value: u32,
816        mask: u32,
817    ) -> AsynResult<()> {
818        self.write_uint32_digital(user, value, mask)
819    }
820
821    fn io_flush(&mut self, _user: &mut AsynUser) -> AsynResult<()> {
822        Ok(())
823    }
824
825    // --- Octet EOS (delegates to interpose stack by default) ---
826
827    fn set_input_eos(&mut self, eos: &[u8]) -> AsynResult<()> {
828        if eos.len() > 2 {
829            return Err(AsynError::Status {
830                status: AsynStatus::Error,
831                message: format!("illegal eoslen {}", eos.len()),
832            });
833        }
834        self.base_mut().input_eos = eos.to_vec();
835        Ok(())
836    }
837
838    fn get_input_eos(&self) -> Vec<u8> {
839        self.base().input_eos.clone()
840    }
841
842    fn set_output_eos(&mut self, eos: &[u8]) -> AsynResult<()> {
843        if eos.len() > 2 {
844            return Err(AsynError::Status {
845                status: AsynStatus::Error,
846                message: format!("illegal eoslen {}", eos.len()),
847            });
848        }
849        self.base_mut().output_eos = eos.to_vec();
850        Ok(())
851    }
852
853    fn get_output_eos(&self) -> Vec<u8> {
854        self.base().output_eos.clone()
855    }
856
857    // --- Lifecycle ---
858
859    /// Called when the port is being shut down. Drivers override this
860    /// to release hardware resources. Matches C asynPortDriver::shutdownPortDriver().
861    fn shutdown(&mut self) -> AsynResult<()> {
862        Ok(())
863    }
864
865    // --- drvUser ---
866
867    /// Resolve a driver info string to a parameter index.
868    /// Default: look up by parameter name.
869    fn drv_user_create(&self, drv_info: &str) -> AsynResult<usize> {
870        self.base()
871            .params
872            .find_param(drv_info)
873            .ok_or_else(|| AsynError::ParamNotFound(drv_info.to_string()))
874    }
875
876    // --- Capabilities ---
877
878    /// Declare the capabilities this driver supports.
879    /// Default implementation includes all scalar read/write operations.
880    fn capabilities(&self) -> Vec<crate::interfaces::Capability> {
881        crate::interfaces::default_capabilities()
882    }
883
884    /// Check if this driver supports a specific capability.
885    fn supports(&self, cap: crate::interfaces::Capability) -> bool {
886        self.capabilities().contains(&cap)
887    }
888
889    fn init(&mut self) -> AsynResult<()> {
890        Ok(())
891    }
892}
893
894#[cfg(test)]
895mod tests {
896    use super::*;
897    struct TestDriver {
898        base: PortDriverBase,
899    }
900
901    impl TestDriver {
902        fn new() -> Self {
903            let mut base = PortDriverBase::new("test", 1, PortFlags::default());
904            base.create_param("VAL", ParamType::Int32).unwrap();
905            base.create_param("TEMP", ParamType::Float64).unwrap();
906            base.create_param("MSG", ParamType::Octet).unwrap();
907            base.create_param("BITS", ParamType::UInt32Digital).unwrap();
908            Self { base }
909        }
910    }
911
912    impl PortDriver for TestDriver {
913        fn base(&self) -> &PortDriverBase {
914            &self.base
915        }
916        fn base_mut(&mut self) -> &mut PortDriverBase {
917            &mut self.base
918        }
919    }
920
921    #[test]
922    fn test_default_read_write_int32() {
923        let mut drv = TestDriver::new();
924        let mut user = AsynUser::new(0);
925        drv.write_int32(&mut user, 42).unwrap();
926        let user = AsynUser::new(0);
927        assert_eq!(drv.read_int32(&user).unwrap(), 42);
928    }
929
930    #[test]
931    fn test_default_read_write_float64() {
932        let mut drv = TestDriver::new();
933        let mut user = AsynUser::new(1);
934        drv.write_float64(&mut user, 3.14).unwrap();
935        let user = AsynUser::new(1);
936        assert!((drv.read_float64(&user).unwrap() - 3.14).abs() < 1e-10);
937    }
938
939    #[test]
940    fn test_default_read_write_octet() {
941        let mut drv = TestDriver::new();
942        let mut user = AsynUser::new(2);
943        drv.write_octet(&mut user, b"hello").unwrap();
944        let user = AsynUser::new(2);
945        let mut buf = [0u8; 32];
946        let n = drv.read_octet(&user, &mut buf).unwrap();
947        assert_eq!(&buf[..n], b"hello");
948    }
949
950    #[test]
951    fn test_default_read_write_uint32() {
952        let mut drv = TestDriver::new();
953        let mut user = AsynUser::new(3);
954        drv.write_uint32_digital(&mut user, 0xFF, 0x0F).unwrap();
955        let user = AsynUser::new(3);
956        assert_eq!(drv.read_uint32_digital(&user, 0xFF).unwrap(), 0x0F);
957    }
958
959    #[test]
960    fn test_connect_disconnect() {
961        let mut drv = TestDriver::new();
962        let user = AsynUser::default();
963        assert!(drv.base().connected);
964        drv.disconnect(&user).unwrap();
965        assert!(!drv.base().connected);
966        drv.connect(&user).unwrap();
967        assert!(drv.base().connected);
968    }
969
970    #[test]
971    fn test_drv_user_create() {
972        let drv = TestDriver::new();
973        assert_eq!(drv.drv_user_create("VAL").unwrap(), 0);
974        assert_eq!(drv.drv_user_create("TEMP").unwrap(), 1);
975        assert!(drv.drv_user_create("NOPE").is_err());
976    }
977
978    #[test]
979    fn test_call_param_callbacks() {
980        let mut drv = TestDriver::new();
981        let mut rx = drv.base_mut().interrupts.subscribe_async();
982
983        drv.base_mut().set_int32_param(0, 0, 100).unwrap();
984        drv.base_mut().set_float64_param(1, 0, 2.0).unwrap();
985        drv.base_mut().call_param_callbacks(0).unwrap();
986
987        let v1 = rx.try_recv().unwrap();
988        assert_eq!(v1.reason, 0);
989        let v2 = rx.try_recv().unwrap();
990        assert_eq!(v2.reason, 1);
991        assert!(rx.try_recv().is_err());
992    }
993
994    #[test]
995    fn test_no_callback_for_unchanged() {
996        let mut drv = TestDriver::new();
997        let mut rx = drv.base_mut().interrupts.subscribe_async();
998
999        drv.base_mut().set_int32_param(0, 0, 5).unwrap();
1000        drv.base_mut().call_param_callbacks(0).unwrap();
1001        let _ = rx.try_recv().unwrap(); // consume
1002
1003        // Set same value — no interrupt
1004        drv.base_mut().set_int32_param(0, 0, 5).unwrap();
1005        drv.base_mut().call_param_callbacks(0).unwrap();
1006        assert!(rx.try_recv().is_err());
1007    }
1008
1009    #[test]
1010    fn test_array_not_supported_by_default() {
1011        let mut drv = TestDriver::new();
1012        let user = AsynUser::new(0);
1013        let mut buf = [0f64; 10];
1014        assert!(drv.read_float64_array(&user, &mut buf).is_err());
1015        assert!(drv.write_float64_array(&user, &[1.0]).is_err());
1016    }
1017
1018    #[test]
1019    fn test_option_set_get() {
1020        let mut drv = TestDriver::new();
1021        drv.set_option("baud", "9600").unwrap();
1022        assert_eq!(drv.get_option("baud").unwrap(), "9600");
1023        drv.set_option("baud", "115200").unwrap();
1024        assert_eq!(drv.get_option("baud").unwrap(), "115200");
1025    }
1026
1027    #[test]
1028    fn test_option_not_found() {
1029        let drv = TestDriver::new();
1030        let err = drv.get_option("nonexistent").unwrap_err();
1031        assert!(matches!(err, AsynError::OptionNotFound(_)));
1032    }
1033
1034    #[test]
1035    fn test_report_no_panic() {
1036        let mut drv = TestDriver::new();
1037        drv.set_option("testkey", "testval").unwrap();
1038        drv.base_mut().set_int32_param(0, 0, 42).unwrap();
1039        for level in 0..=3 {
1040            drv.report(level);
1041        }
1042    }
1043
1044    #[test]
1045    fn test_callback_uses_param_timestamp() {
1046        let mut drv = TestDriver::new();
1047        let mut rx = drv.base_mut().interrupts.subscribe_async();
1048
1049        let custom_ts = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000);
1050        drv.base_mut().set_int32_param(0, 0, 77).unwrap();
1051        drv.base_mut().set_param_timestamp(0, 0, custom_ts).unwrap();
1052        drv.base_mut().call_param_callbacks(0).unwrap();
1053
1054        let v = rx.try_recv().unwrap();
1055        assert_eq!(v.reason, 0);
1056        assert_eq!(v.timestamp, custom_ts);
1057    }
1058
1059    #[test]
1060    fn test_default_read_write_enum() {
1061        use crate::param::EnumEntry;
1062
1063        let mut base = PortDriverBase::new("test_enum", 1, PortFlags::default());
1064        base.create_param("MODE", ParamType::Enum).unwrap();
1065
1066        struct EnumDriver {
1067            base: PortDriverBase,
1068        }
1069        impl PortDriver for EnumDriver {
1070            fn base(&self) -> &PortDriverBase {
1071                &self.base
1072            }
1073            fn base_mut(&mut self) -> &mut PortDriverBase {
1074                &mut self.base
1075            }
1076        }
1077
1078        let mut drv = EnumDriver { base };
1079        let choices: Arc<[EnumEntry]> = Arc::from(vec![
1080            EnumEntry {
1081                string: "Off".into(),
1082                value: 0,
1083                severity: 0,
1084            },
1085            EnumEntry {
1086                string: "On".into(),
1087                value: 1,
1088                severity: 0,
1089            },
1090        ]);
1091        let mut user = AsynUser::new(0);
1092        drv.write_enum_choices(&mut user, choices).unwrap();
1093        drv.write_enum(&mut user, 1).unwrap();
1094        let (idx, ch) = drv.read_enum(&AsynUser::new(0)).unwrap();
1095        assert_eq!(idx, 1);
1096        assert_eq!(ch[1].string, "On");
1097    }
1098
1099    #[test]
1100    fn test_enum_callback() {
1101        use crate::param::{EnumEntry, ParamValue};
1102
1103        let mut base = PortDriverBase::new("test_enum_cb", 1, PortFlags::default());
1104        base.create_param("MODE", ParamType::Enum).unwrap();
1105        let mut rx = base.interrupts.subscribe_async();
1106
1107        struct EnumDriver {
1108            base: PortDriverBase,
1109        }
1110        impl PortDriver for EnumDriver {
1111            fn base(&self) -> &PortDriverBase {
1112                &self.base
1113            }
1114            fn base_mut(&mut self) -> &mut PortDriverBase {
1115                &mut self.base
1116            }
1117        }
1118
1119        let mut drv = EnumDriver { base };
1120        let choices: Arc<[EnumEntry]> = Arc::from(vec![
1121            EnumEntry {
1122                string: "A".into(),
1123                value: 0,
1124                severity: 0,
1125            },
1126            EnumEntry {
1127                string: "B".into(),
1128                value: 1,
1129                severity: 0,
1130            },
1131        ]);
1132        drv.base_mut()
1133            .set_enum_choices_param(0, 0, choices)
1134            .unwrap();
1135        drv.base_mut().set_enum_index_param(0, 0, 1).unwrap();
1136        drv.base_mut().call_param_callbacks(0).unwrap();
1137
1138        let v = rx.try_recv().unwrap();
1139        assert_eq!(v.reason, 0);
1140        assert!(matches!(v.value, ParamValue::Enum { index: 1, .. }));
1141    }
1142
1143    #[test]
1144    fn test_default_read_write_generic_pointer() {
1145        let mut base = PortDriverBase::new("test_gp", 1, PortFlags::default());
1146        base.create_param("PTR", ParamType::GenericPointer).unwrap();
1147
1148        struct GpDriver {
1149            base: PortDriverBase,
1150        }
1151        impl PortDriver for GpDriver {
1152            fn base(&self) -> &PortDriverBase {
1153                &self.base
1154            }
1155            fn base_mut(&mut self) -> &mut PortDriverBase {
1156                &mut self.base
1157            }
1158        }
1159
1160        let mut drv = GpDriver { base };
1161        let data: Arc<dyn std::any::Any + Send + Sync> = Arc::new(99i32);
1162        let mut user = AsynUser::new(0);
1163        drv.write_generic_pointer(&mut user, data).unwrap();
1164        let val = drv.read_generic_pointer(&AsynUser::new(0)).unwrap();
1165        assert_eq!(*val.downcast_ref::<i32>().unwrap(), 99);
1166    }
1167
1168    #[test]
1169    fn test_generic_pointer_callback() {
1170        use crate::param::ParamValue;
1171
1172        let mut base = PortDriverBase::new("test_gp_cb", 1, PortFlags::default());
1173        base.create_param("PTR", ParamType::GenericPointer).unwrap();
1174        let mut rx = base.interrupts.subscribe_async();
1175
1176        struct GpDriver {
1177            base: PortDriverBase,
1178        }
1179        impl PortDriver for GpDriver {
1180            fn base(&self) -> &PortDriverBase {
1181                &self.base
1182            }
1183            fn base_mut(&mut self) -> &mut PortDriverBase {
1184                &mut self.base
1185            }
1186        }
1187
1188        let mut drv = GpDriver { base };
1189        let data: Arc<dyn std::any::Any + Send + Sync> = Arc::new(vec![1, 2, 3]);
1190        drv.base_mut()
1191            .set_generic_pointer_param(0, 0, data)
1192            .unwrap();
1193        drv.base_mut().call_param_callbacks(0).unwrap();
1194
1195        let v = rx.try_recv().unwrap();
1196        assert_eq!(v.reason, 0);
1197        assert!(matches!(v.value, ParamValue::GenericPointer(_)));
1198    }
1199
1200    #[test]
1201    fn test_interpose_push_requires_lock() {
1202        use crate::interpose::{OctetInterpose, OctetNext, OctetReadResult};
1203        use parking_lot::Mutex;
1204        use std::sync::Arc;
1205
1206        struct NoopInterpose;
1207        impl OctetInterpose for NoopInterpose {
1208            fn read(
1209                &mut self,
1210                user: &AsynUser,
1211                buf: &mut [u8],
1212                next: &mut dyn OctetNext,
1213            ) -> AsynResult<OctetReadResult> {
1214                next.read(user, buf)
1215            }
1216            fn write(
1217                &mut self,
1218                user: &mut AsynUser,
1219                data: &[u8],
1220                next: &mut dyn OctetNext,
1221            ) -> AsynResult<usize> {
1222                next.write(user, data)
1223            }
1224            fn flush(&mut self, user: &mut AsynUser, next: &mut dyn OctetNext) -> AsynResult<()> {
1225                next.flush(user)
1226            }
1227        }
1228
1229        let port: Arc<Mutex<dyn PortDriver>> = Arc::new(Mutex::new(TestDriver::new()));
1230
1231        {
1232            let mut guard = port.lock();
1233            guard
1234                .base_mut()
1235                .push_octet_interpose(Box::new(NoopInterpose));
1236            assert_eq!(guard.base().interpose_octet.len(), 1);
1237        }
1238    }
1239
1240    #[test]
1241    fn test_default_read_write_int64() {
1242        let mut base = PortDriverBase::new("test_i64", 1, PortFlags::default());
1243        base.create_param("BIG", ParamType::Int64).unwrap();
1244
1245        struct I64Driver {
1246            base: PortDriverBase,
1247        }
1248        impl PortDriver for I64Driver {
1249            fn base(&self) -> &PortDriverBase {
1250                &self.base
1251            }
1252            fn base_mut(&mut self) -> &mut PortDriverBase {
1253                &mut self.base
1254            }
1255        }
1256
1257        let mut drv = I64Driver { base };
1258        let mut user = AsynUser::new(0);
1259        drv.write_int64(&mut user, i64::MAX).unwrap();
1260        assert_eq!(drv.read_int64(&AsynUser::new(0)).unwrap(), i64::MAX);
1261    }
1262
1263    #[test]
1264    fn test_get_bounds_int64_default() {
1265        let base = PortDriverBase::new("test_bounds", 1, PortFlags::default());
1266        struct BoundsDriver {
1267            base: PortDriverBase,
1268        }
1269        impl PortDriver for BoundsDriver {
1270            fn base(&self) -> &PortDriverBase {
1271                &self.base
1272            }
1273            fn base_mut(&mut self) -> &mut PortDriverBase {
1274                &mut self.base
1275            }
1276        }
1277        let drv = BoundsDriver { base };
1278        let (lo, hi) = drv.get_bounds_int64(&AsynUser::default()).unwrap();
1279        assert_eq!(lo, i64::MIN);
1280        assert_eq!(hi, i64::MAX);
1281    }
1282
1283    #[test]
1284    fn test_per_addr_device_state() {
1285        let mut base = PortDriverBase::new(
1286            "multi",
1287            4,
1288            PortFlags {
1289                multi_device: true,
1290                can_block: false,
1291                destructible: true,
1292            },
1293        );
1294        base.create_param("V", ParamType::Int32).unwrap();
1295
1296        // Default: all connected
1297        assert!(base.is_device_connected(0));
1298        assert!(base.is_device_connected(1));
1299
1300        // Disable addr 1
1301        base.device_state(1).enabled = false;
1302        assert!(base.check_ready_addr(0).is_ok());
1303        let err = base.check_ready_addr(1).unwrap_err();
1304        assert!(format!("{err}").contains("disabled"));
1305
1306        // Disconnect addr 2
1307        base.device_state(2).connected = false;
1308        let err = base.check_ready_addr(2).unwrap_err();
1309        assert!(format!("{err}").contains("disconnected"));
1310    }
1311
1312    #[test]
1313    fn test_per_addr_single_device_ignored() {
1314        let mut base = PortDriverBase::new("single", 1, PortFlags::default());
1315        base.create_param("V", ParamType::Int32).unwrap();
1316        // For single-device, per-addr check passes even if no device state
1317        assert!(base.check_ready_addr(0).is_ok());
1318    }
1319
1320    #[test]
1321    fn test_timestamp_source() {
1322        let mut base = PortDriverBase::new("ts_test", 1, PortFlags::default());
1323        base.create_param("V", ParamType::Int32).unwrap();
1324
1325        let fixed_ts = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(999999);
1326        base.register_timestamp_source(move || fixed_ts);
1327
1328        assert_eq!(base.current_timestamp(), fixed_ts);
1329    }
1330
1331    #[test]
1332    fn test_timestamp_source_in_callbacks() {
1333        let mut base = PortDriverBase::new("ts_cb", 1, PortFlags::default());
1334        base.create_param("V", ParamType::Int32).unwrap();
1335        let mut rx = base.interrupts.subscribe_async();
1336
1337        let fixed_ts = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(123456);
1338        base.register_timestamp_source(move || fixed_ts);
1339
1340        struct TsDriver {
1341            base: PortDriverBase,
1342        }
1343        impl PortDriver for TsDriver {
1344            fn base(&self) -> &PortDriverBase {
1345                &self.base
1346            }
1347            fn base_mut(&mut self) -> &mut PortDriverBase {
1348                &mut self.base
1349            }
1350        }
1351        let mut drv = TsDriver { base };
1352        drv.base_mut().set_int32_param(0, 0, 42).unwrap();
1353        drv.base_mut().call_param_callbacks(0).unwrap();
1354
1355        let v = rx.try_recv().unwrap();
1356        // Should use fixed_ts since no per-param timestamp is set
1357        assert_eq!(v.timestamp, fixed_ts);
1358    }
1359
1360    #[test]
1361    fn test_queue_priority_connect() {
1362        assert!(QueuePriority::Connect > QueuePriority::High);
1363    }
1364
1365    #[test]
1366    fn test_port_flags_destructible_default() {
1367        let flags = PortFlags::default();
1368        assert!(flags.destructible);
1369    }
1370
1371    // --- Phase 2B: per-addr connect/disconnect/enable/disable ---
1372
1373    #[test]
1374    fn test_connect_addr() {
1375        let mut base = PortDriverBase::new(
1376            "multi_conn",
1377            4,
1378            PortFlags {
1379                multi_device: true,
1380                can_block: false,
1381                destructible: true,
1382            },
1383        );
1384        base.create_param("V", ParamType::Int32).unwrap();
1385
1386        base.disconnect_addr(1);
1387        assert!(!base.is_device_connected(1));
1388        assert!(base.check_ready_addr(1).is_err());
1389
1390        base.connect_addr(1);
1391        assert!(base.is_device_connected(1));
1392        assert!(base.check_ready_addr(1).is_ok());
1393    }
1394
1395    #[test]
1396    fn test_enable_disable_addr() {
1397        let mut base = PortDriverBase::new(
1398            "multi_en",
1399            4,
1400            PortFlags {
1401                multi_device: true,
1402                can_block: false,
1403                destructible: true,
1404            },
1405        );
1406        base.create_param("V", ParamType::Int32).unwrap();
1407
1408        base.disable_addr(2);
1409        let err = base.check_ready_addr(2).unwrap_err();
1410        assert!(format!("{err}").contains("disabled"));
1411
1412        base.enable_addr(2);
1413        assert!(base.check_ready_addr(2).is_ok());
1414    }
1415
1416    #[test]
1417    fn test_port_level_overrides_addr() {
1418        let mut base = PortDriverBase::new(
1419            "multi_override",
1420            4,
1421            PortFlags {
1422                multi_device: true,
1423                can_block: false,
1424                destructible: true,
1425            },
1426        );
1427        base.create_param("V", ParamType::Int32).unwrap();
1428
1429        // Port-level disabled overrides addr-level enabled
1430        base.enabled = false;
1431        base.enable_addr(0); // addr 0 is enabled, but port is disabled
1432        let err = base.check_ready_addr(0).unwrap_err();
1433        assert!(format!("{err}").contains("disabled"));
1434    }
1435
1436    #[test]
1437    fn test_per_addr_exception_announced() {
1438        use std::sync::atomic::{AtomicI32, Ordering};
1439
1440        let mut base = PortDriverBase::new(
1441            "multi_exc",
1442            4,
1443            PortFlags {
1444                multi_device: true,
1445                can_block: false,
1446                destructible: true,
1447            },
1448        );
1449        base.create_param("V", ParamType::Int32).unwrap();
1450
1451        let exc_mgr = Arc::new(crate::exception::ExceptionManager::new());
1452        base.exception_sink = Some(exc_mgr.clone());
1453
1454        let last_addr = Arc::new(AtomicI32::new(-99));
1455        let last_addr2 = last_addr.clone();
1456        exc_mgr.add_callback(move |event| {
1457            last_addr2.store(event.addr, Ordering::Relaxed);
1458        });
1459
1460        base.disconnect_addr(3);
1461        assert_eq!(last_addr.load(Ordering::Relaxed), 3);
1462
1463        base.enable_addr(2);
1464        assert_eq!(last_addr.load(Ordering::Relaxed), 2);
1465    }
1466}