1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3
4#[cfg(feature = "async")]
5use futures::{AsyncBufReadExt, AsyncWriteExt};
6use log::debug;
7use std::{
8 borrow::Cow,
9 io::{BufRead, BufReader},
10};
11use thiserror::Error;
12
13pub mod operations;
14use operations::iee4882::IdnOutput;
15use operations::measure::{MeasureAllInfoOutput, MeasureAllOutput};
16
17macro_rules! query_method {
18 (sync, $method: ident, $op:expr, $out:ident) => {
19 pub fn $method(&mut self) -> Result<$out, Error> {
20 self.run_operation($op)
21 }
22 };
23 (async, $method: ident, $op:expr, $out:ident) => {
24 pub async fn $method(&mut self) -> Result<$out, Error> {
25 self.run_operation($op).await
26 }
27 };
28}
29
30macro_rules! set_method {
31 (sync, $method: ident, $param: ident, $set:literal, $ty:ident) => {
32 pub fn $method(&mut self, $param: $ty) -> Result<(), Error> {
33 self.write_command(&format!($set, $param))
34 }
35 };
36
37 (async, $method: ident, $param: ident, $set:literal, $ty:ident) => {
38 pub async fn $method(&mut self, $param: $ty) -> Result<(), Error> {
39 self.write_command(&format!($set, $param)).await
40 }
41 };
42}
43
44macro_rules! write_method {
45 (sync, $method: ident, $set:literal) => {
46 pub fn $method(&mut self) -> Result<(), Error> {
47 self.write_command($set)
48 }
49 };
50
51 (async, $method: ident, $set:literal) => {
52 pub async fn $method(&mut self) -> Result<(), Error> {
53 self.write_command($set).await
54 }
55 };
56}
57
58macro_rules! implement_spe {
59 ($sync_or_async:ident) => {
60 query_method!($sync_or_async, idn, operations::iee4882::Idn {}, IdnOutput);
61 write_method!($sync_or_async, reset, "*RST");
62
63 write_method!($sync_or_async, enable_output, "OUTP ON");
64 write_method!($sync_or_async, disable_output, "OUTP OFF");
65 query_method!($sync_or_async, output, operations::output::Output {}, bool);
66
67 set_method!($sync_or_async, set_volt, volt, "VOLT {}", f32);
68 query_method!($sync_or_async, volt, operations::output::Volt {}, f32);
69
70 set_method!($sync_or_async, set_volt_limit, volt, "VOLT:LIMIT {}", f32);
71 query_method!(
72 $sync_or_async,
73 volt_limit,
74 operations::output::VoltLimit {},
75 f32
76 );
77
78 set_method!($sync_or_async, set_current, current, "CURR {}", f32);
79 query_method!($sync_or_async, current, operations::output::Current {}, f32);
80
81 set_method!(
82 $sync_or_async,
83 set_current_limit,
84 current,
85 "CURR:LIMIT {}",
86 f32
87 );
88 query_method!(
89 $sync_or_async,
90 current_limit,
91 operations::output::CurrentLimit {},
92 f32
93 );
94
95 query_method!(
96 $sync_or_async,
97 measure_volt,
98 operations::measure::Volt {},
99 f32
100 );
101 query_method!(
102 $sync_or_async,
103 measure_current,
104 operations::measure::Current {},
105 f32
106 );
107 query_method!(
108 $sync_or_async,
109 measure_power,
110 operations::measure::Power {},
111 f32
112 );
113
114 query_method!(
115 $sync_or_async,
116 measure_all,
117 operations::measure::MeasureAll {},
118 MeasureAllOutput
119 );
120
121 query_method!(
122 $sync_or_async,
123 measure_all_info,
124 operations::measure::MeasureAllInfo {},
125 MeasureAllInfoOutput
126 );
127 };
128}
129
130#[derive(Debug)]
131pub struct SPE<T>
132where
133 T: std::io::Write + std::io::Read,
134{
135 transport: BufReader<T>,
136}
137
138impl<T> SPE<T>
139where
140 T: std::io::Read + std::io::Write,
141{
142 pub fn new(transport: T) -> Self {
143 Self {
144 transport: BufReader::new(transport),
145 }
146 }
147
148 fn write_command(&mut self, cmd: &str) -> Result<(), Error> {
149 debug!("out: {}", cmd);
150 let writer = self.transport.get_mut();
151 writer.write_all(cmd.as_bytes())?;
152 writer.write_all(b"\r\n")?;
153 writer.flush()?;
154 Ok(())
155 }
156
157 fn run_operation<Op>(&mut self, operation: Op) -> Result<Op::Out, Error>
158 where
159 Op: Operation,
160 {
161 self.write_command(&operation.command())?;
162
163 let mut out: String = String::new();
164 self.transport.read_line(&mut out)?;
165 let trimmed = out.trim_end();
166
167 debug!("in: {}", trimmed);
168 operation.parse_line(trimmed)
169 }
170
171 implement_spe!(sync);
172}
173
174#[cfg(feature = "serialport")]
175#[cfg_attr(docsrs, doc(cfg(feature = "serialport")))]
176mod s {
177 use super::*;
178
179 #[cfg(unix)]
180 type SerialportNative = serialport::TTYPort;
181 #[cfg(windows)]
182 type SerialportNative = serialport::COMPort;
183
184 impl SPE<SerialportNative> {
185 pub fn from_serialport(path: &str) -> Result<Self, serialport::Error> {
186 debug!("Opening: {}", path);
187 let port = serialport::new(path, 115_200)
188 .timeout(std::time::Duration::from_secs(5))
189 .open_native()?;
190 Ok(Self::new(port))
191 }
192 }
193}
194
195#[cfg(feature = "async")]
196#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
197mod a {
198 use super::*;
199 #[derive(Debug)]
200 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
201 pub struct AsyncSPE<T>
202 where
203 T: futures::io::AsyncWrite + futures::io::AsyncRead + Unpin,
204 {
205 transport: futures::io::BufReader<T>,
206 }
207
208 impl<T> AsyncSPE<T>
209 where
210 T: futures::io::AsyncWrite + futures::io::AsyncRead + Unpin,
211 {
212 pub fn new(transport: T) -> Self {
213 Self {
214 transport: futures::io::BufReader::new(transport),
215 }
216 }
217
218 async fn write_command(&mut self, cmd: &str) -> Result<(), Error> {
219 debug!("out: {}", cmd);
220 let writer = self.transport.get_mut();
221 writer.write_all(cmd.as_bytes()).await?;
222 writer.write_all(b"\r\n").await?;
223 writer.flush().await?;
224 Ok(())
225 }
226
227 async fn run_operation<Op>(&mut self, operation: Op) -> Result<Op::Out, Error>
228 where
229 Op: Operation,
230 {
231 self.write_command(&operation.command()).await?;
232
233 let mut out: String = String::new();
234 self.transport.read_line(&mut out).await?;
235 let trimmed = out.trim_end();
236
237 debug!("in: {}", trimmed);
238 operation.parse_line(trimmed)
239 }
240
241 implement_spe!(async);
242 }
243}
244
245#[cfg(feature = "async")]
246#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
247pub use a::AsyncSPE;
248
249#[derive(Debug, Error)]
250pub enum Error {
251 #[error("Unexpected data")]
252 UnexpectedData,
253 #[error("IO Error: {0}")]
254 IOError(#[from] std::io::Error),
255}
256
257trait Operation {
258 type Out: PartialEq + std::fmt::Debug;
259
260 fn command(&self) -> Cow<'_, str>;
261 fn has_output(&self) -> bool {
262 true
263 }
264 fn parse_line(&self, line: &str) -> Result<Self::Out, Error>;
265}