1use super::dialect_io;
2use instrument_core::error::Result;
3use instrument_core::kind::InstrumentKind;
4use instrument_core::scpi::{parse_f64_csv, ScpiSession};
5use instrument_core::scpi_commands;
6use instrument_core::InstrumentSession;
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct VoltageTrace {
11 pub samples: Vec<f64>,
13 pub sample_interval_s: f64,
15}
16
17pub struct Oscilloscope {
19 session: InstrumentSession,
20}
21
22impl Oscilloscope {
23 pub fn new(session: InstrumentSession) -> Self {
24 Self { session }
25 }
26
27 pub fn session(&self) -> &InstrumentSession {
28 &self.session
29 }
30
31 pub fn session_mut(&mut self) -> &mut InstrumentSession {
32 &mut self.session
33 }
34
35 fn dialect(&self) -> &'static instrument_core::DialectProfile {
36 self.session.dialect_for(InstrumentKind::Oscilloscope)
37 }
38
39 fn cmd(&self, key: &str, vars: &[(&str, String)], fallback: String) -> String {
40 dialect_io::try_formatted(self.dialect(), key, vars, fallback)
41 }
42
43 pub fn set_timebase_scale(&mut self, seconds_per_div: f64) -> Result<()> {
45 let cmd = self.cmd(
46 "set_timebase_scale",
47 &[("seconds_per_div", dialect_io::f64_text(seconds_per_div))],
48 scpi_commands::scope_set_timebase_scale(seconds_per_div),
49 );
50 self.session.scpi_mut().write(&cmd)
51 }
52
53 pub fn read_timebase_scale(&mut self) -> Result<f64> {
55 let cmd = dialect_io::try_command(
56 self.dialect(),
57 "read_timebase_scale",
58 scpi_commands::SCOPE_READ_TIMEBASE_SCALE,
59 );
60 let resp = self.session.scpi_mut().query(cmd)?;
61 ScpiSession::parse_f64(&resp)
62 }
63
64 pub fn set_channel_scale(&mut self, channel: u32, volts_per_div: f64) -> Result<()> {
66 let cmd = self.cmd(
67 "set_channel_scale",
68 &[
69 ("channel", channel.to_string()),
70 ("volts_per_div", dialect_io::f64_text(volts_per_div)),
71 ],
72 scpi_commands::scope_set_channel_scale(channel, volts_per_div),
73 );
74 self.session.scpi_mut().write(&cmd)
75 }
76
77 pub fn set_channel_display(&mut self, channel: u32, enabled: bool) -> Result<()> {
79 let state = if enabled { "ON" } else { "OFF" };
80 let cmd = self.cmd(
81 "channel_display",
82 &[("channel", channel.to_string()), ("state", state.into())],
83 scpi_commands::scope_channel_display(channel, state),
84 );
85 self.session.scpi_mut().write(&cmd)
86 }
87
88 pub fn set_channel_coupling(&mut self, channel: u32, coupling: &str) -> Result<()> {
90 let cmd = self.cmd(
91 "channel_coupling",
92 &[
93 ("channel", channel.to_string()),
94 ("coupling", coupling.into()),
95 ],
96 scpi_commands::scope_channel_coupling(channel, coupling),
97 );
98 self.session.scpi_mut().write(&cmd)
99 }
100
101 pub fn set_trigger_source(&mut self, source: &str) -> Result<()> {
103 let cmd = self.cmd(
104 "trigger_source",
105 &[("source", source.into())],
106 scpi_commands::scope_trigger_source(source),
107 );
108 self.session.scpi_mut().write(&cmd)
109 }
110
111 pub fn set_trigger_level(&mut self, volts: f64) -> Result<()> {
113 let cmd = self.cmd(
114 "trigger_level",
115 &[("volts", dialect_io::f64_text(volts))],
116 scpi_commands::scope_trigger_level(volts),
117 );
118 self.session.scpi_mut().write(&cmd)
119 }
120
121 pub fn set_trigger_slope(&mut self, slope: &str) -> Result<()> {
123 let cmd = self.cmd(
124 "trigger_slope",
125 &[("slope", slope.into())],
126 scpi_commands::scope_trigger_slope(slope),
127 );
128 self.session.scpi_mut().write(&cmd)
129 }
130
131 pub fn run(&mut self) -> Result<()> {
133 let cmd = dialect_io::try_command(self.dialect(), "run", scpi_commands::SCOPE_RUN);
134 self.session.scpi_mut().write(cmd)
135 }
136
137 pub fn stop(&mut self) -> Result<()> {
139 let cmd = dialect_io::try_command(self.dialect(), "stop", scpi_commands::SCOPE_STOP);
140 self.session.scpi_mut().write(cmd)
141 }
142
143 pub fn single(&mut self) -> Result<()> {
145 let cmd = dialect_io::try_command(self.dialect(), "single", scpi_commands::SCOPE_SINGLE);
146 self.session.scpi_mut().write(cmd)
147 }
148
149 pub fn measure_vpp(&mut self, channel: u32) -> Result<f64> {
151 let cmd = self.cmd(
152 "measure_vpp",
153 &[("channel", channel.to_string())],
154 scpi_commands::scope_measure_vpp(channel),
155 );
156 let resp = self.session.scpi_mut().query(&cmd)?;
157 ScpiSession::parse_f64(&resp)
158 }
159
160 pub fn measure_frequency(&mut self, channel: u32) -> Result<f64> {
162 let cmd = self.cmd(
163 "measure_frequency",
164 &[("channel", channel.to_string())],
165 scpi_commands::scope_measure_frequency(channel),
166 );
167 let resp = self.session.scpi_mut().query(&cmd)?;
168 ScpiSession::parse_f64(&resp)
169 }
170
171 pub fn capture_voltage_trace(&mut self, channel: u32) -> Result<VoltageTrace> {
173 let source = self.cmd(
174 "waveform_source",
175 &[("channel", channel.to_string())],
176 scpi_commands::scope_set_waveform_source(channel),
177 );
178 let format = dialect_io::try_command(
179 self.dialect(),
180 "waveform_format_ascii",
181 scpi_commands::SCOPE_WAVEFORM_FORMAT_ASCII,
182 );
183 let preamble_cmd = dialect_io::try_command(
184 self.dialect(),
185 "waveform_preamble",
186 scpi_commands::SCOPE_WAVEFORM_PREAMBLE,
187 );
188 let data_cmd = dialect_io::try_command(
189 self.dialect(),
190 "waveform_data",
191 scpi_commands::SCOPE_WAVEFORM_DATA,
192 );
193 let scpi = self.session.scpi_mut();
194 scpi.write(&source)?;
195 scpi.write(format)?;
196 let sample_interval_s = scpi
197 .query(preamble_cmd)
198 .ok()
199 .and_then(|preamble| parse_preamble_x_increment(&preamble))
200 .unwrap_or(0.0);
201 let data = scpi.query(data_cmd)?;
202 let samples = parse_f64_csv(&data)?;
203 Ok(VoltageTrace {
204 samples,
205 sample_interval_s,
206 })
207 }
208}
209
210pub(crate) fn parse_preamble_x_increment(preamble: &str) -> Option<f64> {
211 let fields: Vec<&str> = preamble.split(',').map(str::trim).collect();
212 fields.get(4).and_then(|v| v.parse().ok())
213}