Skip to main content

instrument_core/
session.rs

1use crate::address::ResourceAddress;
2use crate::connect::ConnectOptions;
3use crate::diagnostics::Diagnostics;
4use crate::dialect::{resolve_dialect, DialectProfile};
5use crate::error::{Error, Result};
6use crate::identity::{DeviceIdentity, Idn};
7use crate::ieee4882::Ieee4882;
8use crate::kind::InstrumentKind;
9use crate::scpi::ScpiSession;
10use crate::transport::DynTransport;
11use std::sync::{Arc, Mutex};
12
13/// Opens transport sessions for a device address.
14pub trait SessionOpener: Send + Sync {
15    fn open(&self, address: &ResourceAddress, opts: &ConnectOptions) -> Result<DynTransport>;
16}
17
18/// Active instrument session with SCPI and cached identity.
19pub struct InstrumentSession {
20    pub address: ResourceAddress,
21    pub scpi: ScpiSession,
22    identity: DeviceIdentity,
23}
24
25impl InstrumentSession {
26    pub fn new(
27        address: ResourceAddress,
28        transport: DynTransport,
29        opts: ConnectOptions,
30        identity: DeviceIdentity,
31    ) -> Result<Self> {
32        Self::new_with_diagnostics(address, transport, opts, identity, None)
33    }
34
35    pub fn new_with_diagnostics(
36        address: ResourceAddress,
37        transport: DynTransport,
38        opts: ConnectOptions,
39        identity: DeviceIdentity,
40        diagnostics: Option<Diagnostics>,
41    ) -> Result<Self> {
42        let mut scpi = ScpiSession::new(transport, opts)?;
43        if let Some(diag) = diagnostics {
44            scpi = scpi.with_diagnostics(diag);
45        }
46        Ok(Self {
47            address,
48            scpi,
49            identity,
50        })
51    }
52
53    pub fn address_str(&self) -> &str {
54        &self.address.raw
55    }
56
57    pub fn identity(&self) -> &DeviceIdentity {
58        &self.identity
59    }
60
61    /// Resolves the dialect profile for `kind` using this session's identity.
62    pub fn dialect_for(&self, kind: InstrumentKind) -> &'static DialectProfile {
63        resolve_dialect(
64            kind,
65            self.identity.manufacturer.as_deref(),
66            self.identity.model.as_deref(),
67        )
68    }
69
70    pub fn scpi(&self) -> &ScpiSession {
71        &self.scpi
72    }
73
74    pub fn scpi_mut(&mut self) -> &mut ScpiSession {
75        &mut self.scpi
76    }
77
78    pub fn idn(&mut self) -> Result<Idn> {
79        self.scpi_comm("idn", |scpi| Ieee4882::new(scpi).idn())
80    }
81
82    fn scpi_comm<T>(
83        &mut self,
84        command: &str,
85        f: impl FnOnce(&mut ScpiSession) -> Result<T>,
86    ) -> Result<T> {
87        match f(&mut self.scpi) {
88            Ok(v) => Ok(v),
89            Err(e) => Err(e.with_comm_context(&self.address.raw, Some(command), 1)),
90        }
91    }
92
93    pub fn reset(&mut self) -> Result<()> {
94        self.scpi_comm("*RST", |scpi| Ieee4882::new(scpi).reset())
95    }
96
97    pub fn clear_status(&mut self) -> Result<()> {
98        self.scpi_comm("*CLS", |scpi| Ieee4882::new(scpi).clear_status())
99    }
100
101    pub fn wait_complete(&mut self) -> Result<()> {
102        self.scpi_comm("*OPC?", |scpi| Ieee4882::new(scpi).wait_complete())
103    }
104
105    pub fn check_errors(&mut self) -> Result<Vec<String>> {
106        self.scpi_comm("SYST:ERR?", |scpi| scpi.check_errors())
107    }
108}
109
110/// Reuses a single underlying session across typed views.
111pub struct SessionPool {
112    session: Arc<Mutex<InstrumentSession>>,
113}
114
115impl SessionPool {
116    pub fn new(session: InstrumentSession) -> Self {
117        Self {
118            session: Arc::new(Mutex::new(session)),
119        }
120    }
121
122    pub fn session(&self) -> Arc<Mutex<InstrumentSession>> {
123        self.session.clone()
124    }
125
126    pub fn open_session(&self) -> Result<PooledSession> {
127        Ok(PooledSession {
128            session: self.session.clone(),
129        })
130    }
131}
132
133/// Handle to a pooled session (shared transport).
134pub struct PooledSession {
135    session: Arc<Mutex<InstrumentSession>>,
136}
137
138impl PooledSession {
139    pub fn lock(&self) -> std::sync::MutexGuard<'_, InstrumentSession> {
140        self.session.lock().unwrap()
141    }
142}
143
144/// Validates that a kind is supported before opening a typed view.
145pub fn ensure_kind_supported(
146    address: &ResourceAddress,
147    kind: InstrumentKind,
148    supported: &[InstrumentKind],
149) -> Result<()> {
150    if supported.contains(&kind) {
151        return Ok(());
152    }
153    Err(Error::UnsupportedKind {
154        address: address.raw.clone(),
155        kind,
156        supported: supported.to_vec(),
157    })
158}