1use super::framing::extract_response;
2use super::protocol::{max_write_attempts, normalize_command, parse_f64, SessionCapabilities};
3use crate::connect::ConnectOptions;
4use crate::diagnostics::{CommsEventKind, Diagnostics};
5use crate::error::{Error, Result};
6use crate::ieee4882::Ieee4882;
7use crate::transport::{DynTransport, Transport};
8use std::thread;
9use std::time::{Duration, Instant};
10
11pub struct ScpiSession {
13 transport: DynTransport,
14 opts: ConnectOptions,
15 capabilities: SessionCapabilities,
16 read_buffer: Vec<u8>,
17 diagnostics: Option<Diagnostics>,
18 pending_command: Option<String>,
19}
20
21impl ScpiSession {
22 pub fn new(mut transport: DynTransport, opts: ConnectOptions) -> Result<Self> {
23 transport.configure(&opts)?;
24 let mut session = Self {
25 transport,
26 opts,
27 capabilities: SessionCapabilities::default(),
28 read_buffer: Vec::with_capacity(4096),
29 diagnostics: None,
30 pending_command: None,
31 };
32 if session.opts.reset_on_connect {
33 let _ = Ieee4882::new(&mut session).clear_status();
34 let _ = Ieee4882::new(&mut session).reset();
35 }
36 Ok(session)
37 }
38
39 pub fn with_diagnostics(mut self, diagnostics: Diagnostics) -> Self {
40 self.diagnostics = Some(diagnostics);
41 self
42 }
43
44 pub fn transport(&self) -> &dyn Transport {
45 self.transport.as_ref()
46 }
47
48 pub fn options(&self) -> &ConnectOptions {
49 &self.opts
50 }
51
52 pub fn flush(&mut self) -> Result<()> {
54 let short = Duration::from_millis(50);
55 self.transport.set_read_timeout(short)?;
56 let mut chunk = [0u8; 256];
57 loop {
58 match self.transport.read(&mut chunk) {
59 Ok(0) => break,
60 Ok(_) => continue,
61 Err(Error::Timeout) => break,
62 Err(e) => return Err(e),
63 }
64 }
65 self.read_buffer.clear();
66 Ok(())
67 }
68
69 pub fn write(&mut self, command: &str) -> Result<()> {
71 self.write_with_retry(command, false)
72 }
73
74 pub fn query(&mut self, command: &str) -> Result<String> {
76 self.query_with_timeout(command, self.effective_read_timeout())
77 }
78
79 pub fn query_with_timeout(&mut self, command: &str, timeout: Duration) -> Result<String> {
80 self.write_with_retry(command, true)?;
81 let bytes = self.read_response(timeout)?;
82 Ok(String::from_utf8_lossy(&bytes).trim().to_string())
83 }
84
85 fn write_with_retry(&mut self, command: &str, idempotent: bool) -> Result<()> {
86 let payload = normalize_command(command, &self.opts.terminator);
87 let data = payload.as_bytes();
88
89 let mut attempts = 0;
90 let max_attempts = max_write_attempts(idempotent, self.opts.retries);
91
92 loop {
93 attempts += 1;
94 let started = Instant::now();
95 self.pending_command = Some(command.to_string());
96 match self.transport.write(data) {
97 Ok(()) => {
98 self.record_success(CommsEventKind::WriteOk, Some(command), attempts, started);
99 return Ok(());
100 }
101 Err(Error::Timeout) if attempts < max_attempts => {
102 self.record_failure(
103 CommsEventKind::Timeout,
104 Some(command),
105 attempts,
106 started,
107 "write timeout",
108 );
109 if self.opts.reconnect_on_failure {
110 let _ = self.transport.reconnect();
111 self.record_reconnect();
112 }
113 thread::sleep(self.opts.retry_backoff);
114 }
115 Err(Error::Timeout) => {
116 self.record_failure(
117 CommsEventKind::Timeout,
118 Some(command),
119 attempts,
120 started,
121 "write timeout",
122 );
123 return Err(Error::Timeout);
124 }
125 Err(e) => {
126 self.record_failure(
127 CommsEventKind::WriteFailed,
128 Some(command),
129 attempts,
130 started,
131 &e.to_string(),
132 );
133 return Err(e);
134 }
135 }
136 }
137 }
138
139 fn read_response(&mut self, timeout: Duration) -> Result<Vec<u8>> {
140 self.transport.set_read_timeout(timeout)?;
141 self.read_buffer.clear();
142 let command = self.pending_command.clone();
143
144 let mut chunk = [0u8; 1024];
145 loop {
146 let started = Instant::now();
147 match self.transport.read(&mut chunk) {
148 Ok(0) => thread::sleep(Duration::from_millis(1)),
149 Ok(n) => {
150 self.read_buffer.extend_from_slice(&chunk[..n]);
151 if let Ok((payload, _)) =
152 extract_response(&self.read_buffer, &self.opts.terminator)
153 {
154 self.record_success(CommsEventKind::ReadOk, command.as_deref(), 1, started);
155 return Ok(payload);
156 }
157 }
158 Err(Error::Timeout) => {
159 if !self.read_buffer.is_empty() {
160 if let Ok((payload, _)) =
161 extract_response(&self.read_buffer, &self.opts.terminator)
162 {
163 self.record_success(
164 CommsEventKind::ReadOk,
165 command.as_deref(),
166 1,
167 started,
168 );
169 return Ok(payload);
170 }
171 }
172 if self.opts.reconnect_on_failure {
173 let _ = self.transport.reconnect();
174 self.record_reconnect();
175 }
176 self.record_failure(
177 CommsEventKind::Timeout,
178 command.as_deref(),
179 1,
180 started,
181 "read timeout",
182 );
183 return Err(Error::Timeout);
184 }
185 Err(e) => {
186 self.record_failure(
187 CommsEventKind::ReadFailed,
188 command.as_deref(),
189 1,
190 started,
191 &e.to_string(),
192 );
193 return Err(e);
194 }
195 }
196 }
197 }
198
199 fn record_success(
200 &self,
201 kind: CommsEventKind,
202 command: Option<&str>,
203 attempt: u32,
204 started: Instant,
205 ) {
206 if let Some(diag) = &self.diagnostics {
207 diag.record_success(kind, command, attempt, started.elapsed());
208 }
209 }
210
211 fn record_failure(
212 &self,
213 kind: CommsEventKind,
214 command: Option<&str>,
215 attempt: u32,
216 started: Instant,
217 detail: &str,
218 ) {
219 if let Some(diag) = &self.diagnostics {
220 diag.record_failure(kind, command, attempt, started.elapsed(), detail);
221 }
222 }
223
224 fn record_reconnect(&self) {
225 if let Some(diag) = &self.diagnostics {
226 diag.record_success(CommsEventKind::Reconnect, None, 1, Duration::ZERO);
227 }
228 }
229
230 fn effective_read_timeout(&self) -> Duration {
231 self.opts.per_op_timeout.unwrap_or(self.opts.read_timeout)
232 }
233
234 pub fn probe_syst_err(&mut self) -> bool {
236 if let Some(v) = self.capabilities.syst_err {
237 return v;
238 }
239 let supported = self
240 .query_with_timeout("SYST:ERR?", Duration::from_millis(500))
241 .is_ok();
242 self.capabilities.syst_err = Some(supported);
243 supported
244 }
245
246 pub fn probe_opc(&mut self) -> bool {
248 if let Some(v) = self.capabilities.opc {
249 return v;
250 }
251 let supported = self
252 .query_with_timeout("*OPC?", Duration::from_millis(500))
253 .is_ok();
254 self.capabilities.opc = Some(supported);
255 supported
256 }
257
258 pub fn check_errors(&mut self) -> Result<Vec<String>> {
260 if !self.probe_syst_err() {
261 return Ok(Vec::new());
262 }
263 let mut errors = Vec::new();
264 loop {
265 let resp = self.query("SYST:ERR?")?;
266 if resp.starts_with("0,") || resp.starts_with("+0,") {
267 break;
268 }
269 errors.push(resp);
270 if errors.len() > 50 {
271 break;
272 }
273 }
274 Ok(errors)
275 }
276
277 pub fn parse_f64(response: &str) -> Result<f64> {
279 parse_f64(response)
280 }
281}