Skip to main content

instrument_core/
session.rs

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