kwr103/lib.rs
1//! KWR103 remote controllable power supply
2//!
3//! This crate provides remote control access to Korad KWR103 type power supplies implementing
4//! both, serial/USB and ethernet/UDP based, communication channels.
5//!
6//! # Example
7//! ```no_run
8//! use kwr103::{command::*, Kwr103, TransactionError, UsbConnection};
9//!
10//! fn main() -> Result<(), TransactionError> {
11//! // Establish USB connection
12//! let mut kwr103: Kwr103 = UsbConnection::new("/dev/ttyACM0", 115200, None)?.into();
13//!
14//! // Adjust voltage and current settings
15//! kwr103.command(Voltage(42.0))?;
16//! kwr103.command(Current(1.2))?;
17//!
18//! // Switch output on
19//! kwr103.command(Output(Switch::On))?;
20//!
21//! // Query status, prints e.g. "Output: On, Voltage[V]: 42.000, Current[A]: 0.131"
22//! println!("{}", kwr103.query::<Status>()?);
23//!
24//! Ok(())
25//! }
26//! ```
27//!
28//! # Command Line Interface
29//! To control the power supply from a terminal, you can use the `kwr103` command line tool. Use
30//! `kwr103 --help` for the full specification.
31//!
32//! ```text
33//! > kwr103 status
34//! Output: Off, Voltage[V]: 0.000, Current[A]: 0.000
35//!
36//! > kwr103 output on
37//! > kwr103 status
38//! Output: On, Voltage[V]: 42.000, Current[A]: 0.131
39//! ```
40//!
41//! ## Automatic connection discovery
42//! The `kwr103` command line tool will attempt to automatically find the connection details for
43//! the attached power supply, whether it is serial or ethernet connected.
44//!
45//! In case you want to specify the connection details explicitly instead, use the corresponding
46//! CLI parameters, i.e.
47//!
48//! - `--device=<PATH>` for a serial connected power supply
49//! - `--ip=<IPv4ADDR>` for an ethernet connected power supply
50//!
51//! **In case the automatic discovery finds more than a single power supply unit, no action will be
52//! taken for safety reasons. In this case you will be prompted to specify the connection details
53//! explicitly.**
54
55#![deny(warnings)]
56#![warn(missing_docs)]
57pub mod command;
58pub mod error;
59pub mod eth;
60pub mod usb;
61
62pub use error::{ResponseError, TransactionError};
63pub use eth::EthConnection;
64pub use usb::UsbConnection;
65
66#[doc(hidden)]
67pub mod cli;
68
69/// A command to be issued to the power supply.
70///
71/// Types implementing this trait represent commands that are intended to change settings or the
72/// state of the power supply and do not trigger a response message.
73///
74/// ## Protocol
75///
76/// Most commands follow the simple syntax of
77/// ```text
78/// <CMD>[ID]:<VAL>\n
79/// ```
80/// so in order to set the output voltage to 12.0V on the power supply with device id 1 the
81/// serialized payload should look like
82/// `VSET01:12.0\n`.
83///
84/// If `device_id` is `None`, the `[ID]` field is ommitted.
85///
86/// Additionally, commands may even be concatenated (separated by the newline character), e.g.
87/// `VSET01:42.0\nISET01:2.3\n` both sets the output voltage to 42.0V as well as the output current
88/// to 2.3A
89pub trait Command: Sized {
90 /// Serialize the command to bytes for sending on the serial interface
91 fn serialize(cmd: Self, device_id: Option<u8>) -> Vec<u8>;
92}
93
94/// A query to be issued to the power supply.
95///
96/// Types implementing this trait represent settings or state values that can be queried from the
97/// power supply, i.e. after a query has been issued, the power supply will answer with a
98/// corresponding response.
99///
100/// ## Protocol
101///
102/// Communication follows a simple Query-Response schema where the query is formatted with the
103/// following syntax:
104/// ```text
105/// <QUERY>[ID]?\n
106/// ```
107/// so for example a query for the output voltage setting on the power supply with id 1 serializes
108/// as `VSET01?\n`, with the response following as `42.0\n` (newline terminated value).
109///
110/// If `device_id` is `None`, the `[ID]` field is ommitted.
111///
112/// Additionally, queries may even be concatenated (separated by the newline character),
113/// e.g. `VSET01?\nISET01?\n` queries both the output voltage and current.
114pub trait Query: Sized {
115 /// Serialize to bytes for sending
116 fn serialize(device_id: Option<u8>) -> Vec<u8>;
117
118 /// Parse `bytes` response from the power supply
119 fn parse(bytes: &[u8]) -> std::result::Result<Self, ResponseError>;
120}
121
122/// A type implementing `Transport` defines how to physically communicate with the power supply
123pub trait Transport {
124 /// Attempt to send `bytes` to the power supply
125 fn send(&mut self, bytes: &[u8]) -> Result<(), TransactionError>;
126
127 /// Receive bytes from the power supply
128 fn receive(&mut self) -> Result<Vec<u8>, TransactionError>;
129}
130
131/// A KWR103 type power supply
132///
133/// This is the main access point to control a power supply.
134///
135/// # Note
136/// Rather than instantiating directly, use one of the [`Transport`] types specifying the
137/// connection details.
138///
139/// ```no_run
140/// use kwr103::{Kwr103, UsbConnection};
141///
142/// let mut kwr103 = Kwr103::from(UsbConnection::new("/dev/ttyACM0", 115200, None).unwrap());
143/// ```
144pub struct Kwr103 {
145 transport: Box<dyn Transport>,
146 device_id: Option<u8>,
147}
148
149impl Kwr103 {
150 /// Issue a [`Command`] to the power supply.
151 ///
152 /// Commands do not trigger any response from the power supply, so there is no acknowledgement
153 /// from the power supply that it actually received and accepted the command. Use a
154 /// corresponding [`Kwr103::query`] to check if the command succeeded.
155 ///
156 /// # Example
157 /// ```no_run
158 /// use kwr103::{command::*, Kwr103, UsbConnection};
159 ///
160 /// let mut kwr103 = Kwr103::from(UsbConnection::new("/dev/ttyACM0", 115200, None).unwrap());
161 /// kwr103.command(Voltage(42.0)).unwrap();
162 /// ```
163 pub fn command<C: Command>(&mut self, cmd: C) -> Result<(), TransactionError> {
164 let payload = C::serialize(cmd, self.device_id);
165 self.transport.send(payload.as_slice())
166 }
167
168 /// Issue a [`Query`] to the power supply.
169 ///
170 /// Queries obtain status informations or settings from the power supply and thus involve a
171 /// request-response type communication.
172 ///
173 /// See [`Query`] for details and implementors.
174 ///
175 /// # Example
176 /// ```no_run
177 /// use kwr103::{command::*, Kwr103, UsbConnection};
178 ///
179 /// let mut kwr103 = Kwr103::from(UsbConnection::new("/dev/ttyACM0", 115200, None).unwrap());
180 /// let voltage = kwr103.query::<Voltage>().unwrap();
181 /// println!("Voltage = {:.3}V", voltage.0);
182 /// ```
183 pub fn query<Q: Query>(&mut self) -> Result<Q, TransactionError> {
184 let payload = Q::serialize(self.device_id);
185 self.transport.send(payload.as_slice())?;
186
187 let response = self.transport.receive()?;
188 Ok(Q::parse(&response)?)
189 }
190}