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}