1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#![cfg_attr(not(feature = "std"), no_std)]

//! ![Quickstart](https://github.com/Atmelfan/scpi-rs/workflows/Quickstart/badge.svg)
//! ![Fuzzing](https://github.com/Atmelfan/scpi-rs/workflows/Fuzzing/badge.svg)
//! [![codecov](https://codecov.io/gh/Atmelfan/scpi-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/Atmelfan/scpi-rs)
//! [![](http://meritbadge.herokuapp.com/scpi)](https://crates.io/crates/scpi)
//! [![](https://img.shields.io/github/license/Atmelfan/scpi-rs)](https://img.shields.io/github/license/Atmelfan/scpi-rs)
//!
//! This crate attempts to implement the IEE488.2 / SCPI protocol commonly used by measurement instruments and tools.
//!
//! * [SCPI-1999](http://www.ivifoundation.org/docs/scpi-99.pdf)
//! * [IEEE 488.2](http://dx.doi.org/10.1109/IEEESTD.2004.95390)
//!
//! It does not require the std library (ie it's `no_std` compatible) or a system allocator (useful for embedded).
//!
//!
//! # Scope
//! The crate does not support any transport layer, it only reads ascii-strings (`[u8]`) and writes ascii responses.
//!
//! It does not implement any higher level functions/error handling other than SCPI parsing and response generation.
//! See [scpi-contrib](https://crates.io/crates/scpi) for higher level abstractions.
//!
//! # Using this crate
//! Add `scpi` to your dependencies:
//! ```toml
//! [dependencies]
//! scpi = "1.0"
//! ```
//!
//! # Features
#![doc = document_features::document_features!()]
//! (See rustdoc/docs.rs for available features)
//!
//! # Getting started
//! Look at the [`example`](https://github.com/Atmelfan/scpi-rs/tree/master/example) for how to create a tree and run commands.
//!
//! 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)
//!
//! # Character coding
//! SCPI is strictly ASCII and will throw a error InvalidCharacter if any non-ascii `(>127)` characters are encountered (Exception: Arbitrary data blocks).
//!
//! String parameters and reponse data should use byte-slices (`&[u8]`) with valid ASCII data.
//!
//! The str type can be decoded from either a string parameter or arbitrary block and will automatically be checked for UTF8 encoding.
//! When used as response data a str will always return an arbitrary block.
//!
//! # Error handling
//! The `Node::run(...)` function aborts execution and returns on the first error it encounters.
//!
//! User commands will often use functions which may return an error, these should mostly be propagated down to the parser by rusts `?` operator.
//!
//!
//! # Limitations and differences
//! * Overlapping commands are not supported, [Github issue](https://github.com/Atmelfan/scpi-rs/issues/23).
//!
//! # Contribution
//! Contributions are welcome.
//!
//! # Project organisation:
//!  * `scpi` - Core parser crate
//!  * `scpi-contrib` - Mandatory command implementations and higher level abstractions
//!  * `scpi-derive` - Macro derive library, manly used to generate enum type parameters (see [option::ScpiEnum]).
//!

#[cfg(all(feature = "alloc", not(feature = "std")))]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;

use crate::error::Error;
use core::any::Any;

pub mod error;
pub mod option;
pub mod parser;
pub mod tree;

/// Prelude containing the most useful stuff
///
pub mod prelude {
    pub use crate::{
        error::{Error, ErrorCode},
        Context, Device,
    };
}

/// Re-export supported uom types if enabled
#[cfg(feature = "uom")]
pub mod units {
    #[doc(no_inline)]
    pub use uom;

    #[cfg(feature = "unit-angle")]
    pub use uom::si::f32::Angle;
    #[cfg(feature = "unit-capacitance")]
    pub use uom::si::f32::Capacitance;
    #[cfg(feature = "unit-electric-charge")]
    pub use uom::si::f32::ElectricCharge;
    #[cfg(feature = "unit-electric-current")]
    pub use uom::si::f32::ElectricCurrent;
    #[cfg(feature = "unit-electric-potential")]
    pub use uom::si::f32::ElectricPotential;
    #[cfg(feature = "unit-electrical-conductance")]
    pub use uom::si::f32::ElectricalConductance;
    #[cfg(feature = "unit-electrical-resistance")]
    pub use uom::si::f32::ElectricalResistance;
    #[cfg(feature = "unit-energy")]
    pub use uom::si::f32::Energy;
    #[cfg(feature = "unit-frequency")]
    pub use uom::si::f32::Frequency;
    #[cfg(feature = "unit-inductance")]
    pub use uom::si::f32::Inductance;
    #[cfg(feature = "unit-power")]
    pub use uom::si::f32::Power;
    #[cfg(feature = "unit-ratio")]
    pub use uom::si::f32::Ratio;
    #[cfg(feature = "unit-thermodynamic-temperature")]
    pub use uom::si::f32::ThermodynamicTemperature;
    #[cfg(feature = "unit-time")]
    pub use uom::si::f32::Time;
}

/// A basic device capable of executing commands and not much else
pub trait Device {
    /// Called when the parser encounters a syntax error or a command handler returns an error.
    fn handle_error(&mut self, err: Error);
}

/// Context in which to execute a message.
///
/// Useful when multiple sources can execute commands.
#[derive(Debug)]
pub struct Context<'a> {
    /// Does output buffer contain data?
    pub mav: bool,

    /// User context data.
    ///
    /// **Do not use this to pass application data!**
    /// Use traits instead. It's only intended to pass along data from whatever context is running a command.
    ///
    /// For example: User authentication information if the call comes from an authenticated interface
    /// or port number if the call comes from a serial port.
    pub user: &'a dyn Any,
}

impl<'a> Default for Context<'a> {
    fn default() -> Self {
        Self::new()
    }
}

impl<'a> Context<'a> {
    /// Create a new context
    pub fn new() -> Self {
        Context {
            mav: false,
            user: &(),
        }
    }

    // Create a new context with user data
    pub fn new_with_user(user: &'a dyn Any) -> Self {
        Context { mav: false, user }
    }

    /// Get user context data.
    ///
    /// **DO NOT USE FOR APPLICATION DATA**
    pub fn user<U: Any>(&'a self) -> Option<&'a U> {
        self.user.downcast_ref()
    }

    /// Returns true if output buffer contains data
    pub fn mav(&self) -> bool {
        self.mav
    }
}

#[cfg(test)]
mod tests {
    macro_rules! fixture_device {
        ($dev:ident) => {
            impl $crate::Device for $dev {
                fn handle_error(&mut self, _err: $crate::error::Error) {}
            }
        };
    }
    pub(crate) use fixture_device;
}