scpi/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3//! ![Quickstart](https://github.com/Atmelfan/scpi-rs/workflows/Quickstart/badge.svg)
4//! ![Fuzzing](https://github.com/Atmelfan/scpi-rs/workflows/Fuzzing/badge.svg)
5//! [![codecov](https://codecov.io/gh/Atmelfan/scpi-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/Atmelfan/scpi-rs)
6//! [![](http://meritbadge.herokuapp.com/scpi)](https://crates.io/crates/scpi)
7//! [![](https://img.shields.io/github/license/Atmelfan/scpi-rs)](https://img.shields.io/github/license/Atmelfan/scpi-rs)
8//!
9//! This crate attempts to implement the IEE488.2 / SCPI protocol commonly used by measurement instruments and tools.
10//!
11//! * [SCPI-1999](http://www.ivifoundation.org/docs/scpi-99.pdf)
12//! * [IEEE 488.2](http://dx.doi.org/10.1109/IEEESTD.2004.95390)
13//!
14//! It does not require the std library (ie it's `no_std` compatible) or a system allocator (useful for embedded).
15//!
16//!
17//! # Scope
18//! The crate does not support any transport layer, it only reads ascii-strings (`[u8]`) and writes ascii responses.
19//!
20//! It does not implement any higher level functions/error handling other than SCPI parsing and response generation.
21//! See [scpi-contrib](https://crates.io/crates/scpi) for higher level abstractions.
22//!
23//! # Using this crate
24//! Add `scpi` to your dependencies:
25//! ```toml
26//! [dependencies]
27//! scpi = "1.0"
28//! ```
29//!
30//! # Features
31#![doc = document_features::document_features!()]
32//! (See rustdoc/docs.rs for available features)
33//!
34//! # Getting started
35//! Look at the [`example`](https://github.com/Atmelfan/scpi-rs/tree/master/example) for how to create a tree and run commands.
36//!
37//! Here's a good resource general SCPI style and good practices: [Keysight SCPI Training slides](https://www.keysight.com/us/en/assets/9921-01873/miscellaneous/SCPITrainingSlides.pdf)
38//!
39//! # Character coding
40//! SCPI is strictly ASCII and will throw a error InvalidCharacter if any non-ascii `(>127)` characters are encountered (Exception: Arbitrary data blocks).
41//!
42//! String parameters and reponse data should use byte-slices (`&[u8]`) with valid ASCII data.
43//!
44//! The str type can be decoded from either a string parameter or arbitrary block and will automatically be checked for UTF8 encoding.
45//! When used as response data a str will always return an arbitrary block.
46//!
47//! # Error handling
48//! The `Node::run(...)` function aborts execution and returns on the first error it encounters.
49//!
50//! User commands will often use functions which may return an error, these should mostly be propagated down to the parser by rusts `?` operator.
51//!
52//!
53//! # Limitations and differences
54//! * Overlapping commands are not supported, [Github issue](https://github.com/Atmelfan/scpi-rs/issues/23).
55//!
56//! # Contribution
57//! Contributions are welcome.
58//!
59//! # Project organisation:
60//!  * `scpi` - Core parser crate
61//!  * `scpi-contrib` - Mandatory command implementations and higher level abstractions
62//!  * `scpi-derive` - Macro derive library, manly used to generate enum type parameters (see [option::ScpiEnum]).
63//!
64
65#[cfg(all(feature = "alloc", not(feature = "std")))]
66extern crate alloc;
67#[cfg(feature = "std")]
68extern crate std as alloc;
69
70use crate::error::Error;
71use core::any::Any;
72
73pub mod error;
74pub mod option;
75pub mod parser;
76pub mod tree;
77
78/// Prelude containing the most useful stuff
79///
80pub mod prelude {
81    pub use crate::{
82        error::{Error, ErrorCode},
83        Context, Device,
84    };
85}
86
87/// Re-export supported uom types if enabled
88#[cfg(feature = "uom")]
89pub mod units {
90    #[doc(no_inline)]
91    pub use uom;
92
93    #[cfg(feature = "unit-angle")]
94    pub use uom::si::f32::Angle;
95    #[cfg(feature = "unit-capacitance")]
96    pub use uom::si::f32::Capacitance;
97    #[cfg(feature = "unit-electric-charge")]
98    pub use uom::si::f32::ElectricCharge;
99    #[cfg(feature = "unit-electric-current")]
100    pub use uom::si::f32::ElectricCurrent;
101    #[cfg(feature = "unit-electric-potential")]
102    pub use uom::si::f32::ElectricPotential;
103    #[cfg(feature = "unit-electrical-conductance")]
104    pub use uom::si::f32::ElectricalConductance;
105    #[cfg(feature = "unit-electrical-resistance")]
106    pub use uom::si::f32::ElectricalResistance;
107    #[cfg(feature = "unit-energy")]
108    pub use uom::si::f32::Energy;
109    #[cfg(feature = "unit-frequency")]
110    pub use uom::si::f32::Frequency;
111    #[cfg(feature = "unit-inductance")]
112    pub use uom::si::f32::Inductance;
113    #[cfg(feature = "unit-power")]
114    pub use uom::si::f32::Power;
115    #[cfg(feature = "unit-ratio")]
116    pub use uom::si::f32::Ratio;
117    #[cfg(feature = "unit-thermodynamic-temperature")]
118    pub use uom::si::f32::ThermodynamicTemperature;
119    #[cfg(feature = "unit-time")]
120    pub use uom::si::f32::Time;
121}
122
123/// A basic device capable of executing commands and not much else
124pub trait Device {
125    /// Called when the parser encounters a syntax error or a command handler returns an error.
126    fn handle_error(&mut self, err: Error);
127}
128
129/// Context in which to execute a message.
130///
131/// Useful when multiple sources can execute commands.
132#[derive(Debug)]
133pub struct Context<'a> {
134    /// Does output buffer contain data?
135    pub mav: bool,
136
137    /// User context data.
138    ///
139    /// **Do not use this to pass application data!**
140    /// Use traits instead. It's only intended to pass along data from whatever context is running a command.
141    ///
142    /// For example: User authentication information if the call comes from an authenticated interface
143    /// or port number if the call comes from a serial port.
144    pub user: &'a dyn Any,
145}
146
147impl<'a> Default for Context<'a> {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153impl<'a> Context<'a> {
154    /// Create a new context
155    pub fn new() -> Self {
156        Context {
157            mav: false,
158            user: &(),
159        }
160    }
161
162    // Create a new context with user data
163    pub fn new_with_user(user: &'a dyn Any) -> Self {
164        Context { mav: false, user }
165    }
166
167    /// Get user context data.
168    ///
169    /// **DO NOT USE FOR APPLICATION DATA**
170    pub fn user<U: Any>(&'a self) -> Option<&'a U> {
171        self.user.downcast_ref()
172    }
173
174    /// Returns true if output buffer contains data
175    pub fn mav(&self) -> bool {
176        self.mav
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    macro_rules! fixture_device {
183        ($dev:ident) => {
184            impl $crate::Device for $dev {
185                fn handle_error(&mut self, _err: $crate::error::Error) {}
186            }
187        };
188    }
189    pub(crate) use fixture_device;
190}