2020-08-28 15:48:13 +08:00
|
|
|
use scpi::error::Result;
|
|
|
|
use scpi::expression::numeric_list;
|
|
|
|
use scpi::expression::numeric_list::NumericList;
|
|
|
|
use scpi::format::{Arbitrary, Character};
|
|
|
|
use scpi::prelude::*;
|
|
|
|
use scpi::NumericValues;
|
|
|
|
|
|
|
|
use core::convert::{TryFrom, TryInto};
|
2020-09-04 17:02:05 +08:00
|
|
|
use core::str;
|
2020-09-07 14:07:39 +08:00
|
|
|
use core::str::Utf8Error;
|
2020-08-28 15:48:13 +08:00
|
|
|
use scpi::ieee488::commands::*;
|
|
|
|
use scpi::scpi::commands::*;
|
|
|
|
use scpi::{
|
|
|
|
ieee488_cls,
|
|
|
|
ieee488_ese,
|
|
|
|
ieee488_esr,
|
|
|
|
ieee488_idn,
|
|
|
|
ieee488_opc,
|
|
|
|
ieee488_rst,
|
|
|
|
ieee488_sre,
|
|
|
|
ieee488_stb,
|
|
|
|
ieee488_tst,
|
|
|
|
ieee488_wai,
|
|
|
|
nquery,
|
|
|
|
//Helpers
|
|
|
|
qonly,
|
|
|
|
scpi_crate_version,
|
|
|
|
scpi_status,
|
|
|
|
scpi_system,
|
|
|
|
};
|
2020-09-13 00:58:58 +08:00
|
|
|
use scpi::suffix::{Amplitude, Db};
|
|
|
|
use uom::si::frequency::{gigahertz, hertz, kilohertz, megahertz, Frequency};
|
|
|
|
use uom::si::{f32, f64};
|
2020-08-28 15:48:13 +08:00
|
|
|
|
2020-08-31 16:48:21 +08:00
|
|
|
use embedded_hal::blocking::spi::Transfer;
|
|
|
|
|
2020-09-07 14:07:39 +08:00
|
|
|
use crate::{
|
|
|
|
Urukul,
|
|
|
|
UrukulTraits,
|
|
|
|
Error as UrukulError,
|
|
|
|
ClockSource,
|
|
|
|
};
|
2020-08-31 13:32:08 +08:00
|
|
|
|
2020-09-13 00:58:58 +08:00
|
|
|
use log::{trace, debug, info, warn};
|
|
|
|
|
2020-09-04 17:02:05 +08:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! recursive_scpi_tree {
|
|
|
|
// Handle optional headers (end-node)
|
|
|
|
([$header_name: expr] => $handler: ident) => {
|
|
|
|
Node {
|
|
|
|
name: str::as_bytes($header_name),
|
|
|
|
handler: Some(&$handler{}),
|
|
|
|
sub: &[],
|
|
|
|
optional: true,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Handle non-optinal header (end-node)
|
|
|
|
($header_name: expr => $handler: ident) => {
|
|
|
|
Node {
|
|
|
|
name: str::as_bytes($header_name),
|
|
|
|
handler: Some(&$handler{}),
|
|
|
|
sub: &[],
|
|
|
|
optional: false,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Handle optional header with sub-commands
|
2020-09-07 14:07:39 +08:00
|
|
|
([$header_name: expr] => {$($($rest: tt)=>*),*}) => {
|
2020-09-04 17:02:05 +08:00
|
|
|
Node {
|
|
|
|
name: str::as_bytes($header_name),
|
|
|
|
handler: None,
|
|
|
|
sub: &[
|
|
|
|
$(
|
|
|
|
recursive_scpi_tree!($($rest)=>*),
|
|
|
|
)*
|
|
|
|
],
|
|
|
|
optional: true,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Handle non-optional header with sub-commands
|
2020-09-07 14:07:39 +08:00
|
|
|
($header_name: expr => {$($($rest: tt)=>*),*}) => {
|
2020-09-04 17:02:05 +08:00
|
|
|
Node {
|
|
|
|
name: str::as_bytes($header_name),
|
|
|
|
handler: None,
|
|
|
|
sub: &[
|
|
|
|
$(
|
|
|
|
recursive_scpi_tree!($($rest)=>*),
|
|
|
|
)*
|
|
|
|
],
|
|
|
|
optional: false,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! scpi_root {
|
2020-09-07 14:07:39 +08:00
|
|
|
($($($node: tt)=>*),*) => {
|
2020-09-04 17:02:05 +08:00
|
|
|
&Node {
|
|
|
|
name: b"ROOT",
|
|
|
|
optional: false,
|
|
|
|
handler: None,
|
|
|
|
sub: &[
|
|
|
|
// Create default IEEE488 mandated commands
|
|
|
|
ieee488_cls!(),
|
|
|
|
ieee488_ese!(),
|
|
|
|
ieee488_esr!(),
|
|
|
|
ieee488_idn!(b"manufacturer", b"model", b"serial", b"0.1.2"),
|
|
|
|
ieee488_opc!(),
|
|
|
|
ieee488_rst!(),
|
|
|
|
ieee488_sre!(),
|
|
|
|
ieee488_stb!(),
|
|
|
|
ieee488_tst!(),
|
|
|
|
ieee488_wai!(),
|
|
|
|
// Create default SCPI mandated STATus subsystem
|
|
|
|
scpi_status!(),
|
|
|
|
// Create default SCPI mandated SYSTem subsystem
|
|
|
|
scpi_system!(),
|
|
|
|
//
|
|
|
|
scpi_crate_version!(),
|
|
|
|
$(
|
|
|
|
recursive_scpi_tree!($($node)=>*),
|
|
|
|
)*
|
|
|
|
]
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2020-08-28 15:48:13 +08:00
|
|
|
|
|
|
|
pub struct HelloWorldCommand {}
|
2020-09-03 17:41:27 +08:00
|
|
|
impl<T: Device> Command<T> for HelloWorldCommand {
|
2020-08-28 15:48:13 +08:00
|
|
|
qonly!();
|
|
|
|
|
|
|
|
fn query(
|
|
|
|
&self,
|
2020-09-03 17:41:27 +08:00
|
|
|
_context: &mut Context<T>,
|
2020-08-28 15:48:13 +08:00
|
|
|
_args: &mut Tokenizer,
|
|
|
|
response: &mut ResponseUnit,
|
|
|
|
) -> Result<()> {
|
|
|
|
response.data(b"Hello world" as &[u8]).finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-07 14:07:39 +08:00
|
|
|
pub struct Channel0SwitchCommand {}
|
|
|
|
pub struct Channel1SwitchCommand {}
|
|
|
|
pub struct Channel2SwitchCommand {}
|
|
|
|
pub struct Channel3SwitchCommand {}
|
|
|
|
pub struct ClockSourceCommand {}
|
|
|
|
pub struct ClockDivisionCommand {}
|
2020-09-04 17:52:37 +08:00
|
|
|
|
2020-09-07 14:07:39 +08:00
|
|
|
impl<T: Device + UrukulTraits> Command<T> for Channel0SwitchCommand {
|
|
|
|
nquery!();
|
|
|
|
|
|
|
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
|
|
|
match context.device.get_channel_switch_status(0) {
|
|
|
|
Ok(status) => {
|
|
|
|
let next_state: bool = match args.next_data(true) {
|
|
|
|
Ok(Some(token)) => token.try_into()?,
|
|
|
|
Ok(None) => !status,
|
|
|
|
Err(_) => return Err(ErrorCode::IllegalParameterValue.into()),
|
|
|
|
};
|
|
|
|
match context.device.set_channel_switch(0, next_state) {
|
|
|
|
Ok(()) => Ok(()),
|
|
|
|
Err(_) => Err(Error::new(ErrorCode::HardwareError)),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(_) => Err(Error::new(ErrorCode::HardwareError)),
|
2020-09-04 17:52:37 +08:00
|
|
|
}
|
2020-09-07 14:07:39 +08:00
|
|
|
}
|
2020-09-04 17:52:37 +08:00
|
|
|
}
|
|
|
|
|
2020-09-04 13:29:50 +08:00
|
|
|
impl<T: Device + UrukulTraits> Command<T> for Channel1SwitchCommand {
|
2020-09-03 17:41:27 +08:00
|
|
|
nquery!();
|
|
|
|
|
|
|
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
2020-09-04 13:29:50 +08:00
|
|
|
match context.device.get_channel_switch_status(1) {
|
|
|
|
Ok(status) => {
|
|
|
|
let next_state: bool = match args.next_data(true) {
|
|
|
|
Ok(Some(token)) => token.try_into()?,
|
|
|
|
Ok(None) => !status,
|
|
|
|
Err(_) => return Err(ErrorCode::IllegalParameterValue.into()),
|
|
|
|
};
|
|
|
|
match context.device.set_channel_switch(1, next_state) {
|
|
|
|
Ok(()) => Ok(()),
|
|
|
|
Err(_) => Err(Error::new(ErrorCode::HardwareError)),
|
|
|
|
}
|
|
|
|
},
|
2020-09-03 17:41:27 +08:00
|
|
|
Err(_) => Err(Error::new(ErrorCode::HardwareError)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-07 14:07:39 +08:00
|
|
|
impl<T: Device + UrukulTraits> Command<T> for Channel2SwitchCommand {
|
|
|
|
nquery!();
|
|
|
|
|
|
|
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
|
|
|
match context.device.get_channel_switch_status(2) {
|
|
|
|
Ok(status) => {
|
|
|
|
let next_state: bool = match args.next_data(true) {
|
|
|
|
Ok(Some(token)) => token.try_into()?,
|
|
|
|
Ok(None) => !status,
|
|
|
|
Err(_) => return Err(ErrorCode::IllegalParameterValue.into()),
|
|
|
|
};
|
|
|
|
match context.device.set_channel_switch(2, next_state) {
|
|
|
|
Ok(()) => Ok(()),
|
|
|
|
Err(_) => Err(Error::new(ErrorCode::HardwareError)),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(_) => Err(Error::new(ErrorCode::HardwareError)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Device + UrukulTraits> Command<T> for Channel3SwitchCommand {
|
|
|
|
nquery!();
|
|
|
|
|
|
|
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
|
|
|
match context.device.get_channel_switch_status(3) {
|
|
|
|
Ok(status) => {
|
|
|
|
let next_state: bool = match args.next_data(true) {
|
|
|
|
Ok(Some(token)) => token.try_into()?,
|
|
|
|
Ok(None) => !status,
|
|
|
|
Err(_) => return Err(ErrorCode::IllegalParameterValue.into()),
|
|
|
|
};
|
|
|
|
match context.device.set_channel_switch(3, next_state) {
|
|
|
|
Ok(()) => Ok(()),
|
|
|
|
Err(_) => Err(Error::new(ErrorCode::HardwareError)),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(_) => Err(Error::new(ErrorCode::HardwareError)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T:Device + UrukulTraits> Command<T> for ClockSourceCommand {
|
|
|
|
nquery!();
|
|
|
|
|
|
|
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
2020-09-13 00:58:58 +08:00
|
|
|
|
|
|
|
let s: &[u8] = match args.next_data(false)? {
|
2020-09-07 14:07:39 +08:00
|
|
|
Some(Token::CharacterProgramData(s)) => s,
|
|
|
|
_ => return Err(ErrorCode::IllegalParameterValue.into()),
|
|
|
|
};
|
2020-09-13 00:58:58 +08:00
|
|
|
/*
|
|
|
|
debug!("Converted data: {:?}", data);
|
2020-09-07 17:52:37 +08:00
|
|
|
|
|
|
|
if let Ok(str_param) = str::from_utf8(data) {
|
|
|
|
if let Ok(cmd) = match str_param {
|
2020-09-07 14:07:39 +08:00
|
|
|
"OSC" => context.device.set_clock_source(ClockSource::OSC),
|
|
|
|
"MMCX" => context.device.set_clock_source(ClockSource::MMCX),
|
|
|
|
"SMA" => context.device.set_clock_source(ClockSource::SMA),
|
2020-09-13 00:58:58 +08:00
|
|
|
_ => {
|
|
|
|
return Err(ErrorCode::IllegalParameterValue.into());
|
|
|
|
}
|
2020-09-07 17:52:37 +08:00
|
|
|
} {
|
2020-09-13 00:58:58 +08:00
|
|
|
debug!("Finished parsing source");
|
2020-09-07 14:07:39 +08:00
|
|
|
}
|
2020-09-13 00:58:58 +08:00
|
|
|
} else {
|
|
|
|
return Err(ErrorCode::IllegalParameterValue.into());
|
|
|
|
}
|
|
|
|
let s: &[u8] = args
|
|
|
|
.next_data(false)?
|
|
|
|
.unwrap_or(Token::CharacterProgramData(b"default"))
|
|
|
|
.try_into()?;
|
|
|
|
*/
|
|
|
|
let s_str: &str = str::from_utf8(s)
|
|
|
|
.map_err(|_| ErrorCode::CharacterDataError)?;
|
|
|
|
match s_str {
|
|
|
|
source if source.eq_ignore_ascii_case("OSC") => {
|
|
|
|
trace!("Changing clock source to OSC");
|
|
|
|
context.device.set_clock_source(ClockSource::OSC);
|
|
|
|
},
|
|
|
|
source if source.eq_ignore_ascii_case("MMCX") => {
|
|
|
|
trace!("Changing clock source to MMCX");
|
|
|
|
context.device.set_clock_source(ClockSource::MMCX);
|
|
|
|
},
|
|
|
|
source if source.eq_ignore_ascii_case("SMA") => {
|
|
|
|
trace!("Changing clocksource to SMA");
|
|
|
|
context.device.set_clock_source(ClockSource::SMA);
|
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
warn!("Clock selection failed! Argument error!");
|
|
|
|
return Err(ErrorCode::IllegalParameterValue.into());
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let frequency: f64::Frequency = args.next_data(true)?
|
|
|
|
.map_or(Ok(f64::Frequency::new::<hertz>(0.0)), |t| {
|
|
|
|
t.numeric(|s| match s {
|
|
|
|
NumericValues::Default => Ok(f64::Frequency::new::<hertz>(0.0)),
|
|
|
|
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
|
|
|
})
|
|
|
|
})?;
|
|
|
|
debug!("Received frequency: {:?}", frequency);
|
|
|
|
Ok(())
|
2020-09-07 14:07:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T:Device + UrukulTraits> Command<T> for ClockDivisionCommand {
|
|
|
|
nquery!();
|
|
|
|
|
|
|
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
|
|
|
let data :u8 = match args.next_data(false)? {
|
|
|
|
Some(token) => {
|
|
|
|
match f32::try_from(token) {
|
|
|
|
Ok(val) => {
|
|
|
|
if val == 1.0 || val == 2.0 || val == 4.0 {
|
|
|
|
val as u8
|
|
|
|
} else {
|
|
|
|
return Err(ErrorCode::IllegalParameterValue.into())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(_e) => {
|
|
|
|
return Err(ErrorCode::IllegalParameterValue.into())
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => return Err(ErrorCode::IllegalParameterValue.into()),
|
|
|
|
};
|
|
|
|
match context.device.set_clock_division(data) {
|
|
|
|
Ok(()) => Ok(()),
|
|
|
|
Err(_) => Err(Error::new(ErrorCode::HardwareError)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 13:32:08 +08:00
|
|
|
/*
|
|
|
|
* Implement "Device" trait from SCPI
|
|
|
|
* TODO: Implement mandatory commands
|
|
|
|
*/
|
2020-08-31 16:48:21 +08:00
|
|
|
impl<SPI, E> Device for Urukul<SPI>
|
|
|
|
where
|
|
|
|
SPI: Transfer<u8, Error = E>
|
|
|
|
{
|
2020-08-28 15:48:13 +08:00
|
|
|
fn cls(&mut self) -> Result<()> {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn rst(&mut self) -> Result<()> {
|
2020-08-31 16:48:21 +08:00
|
|
|
match self.reset() {
|
|
|
|
Ok(_) => Ok(()),
|
2020-09-03 17:41:27 +08:00
|
|
|
Err(_) => Err(Error::new(ErrorCode::HardwareError))
|
2020-08-31 16:48:21 +08:00
|
|
|
}
|
2020-08-28 15:48:13 +08:00
|
|
|
}
|
2020-08-31 17:43:15 +08:00
|
|
|
|
|
|
|
fn tst(&mut self) -> Result<()> {
|
|
|
|
match self.test() {
|
|
|
|
Ok(0) => Ok(()),
|
2020-09-03 17:41:27 +08:00
|
|
|
Ok(_) => Err(Error::new(ErrorCode::SelfTestFailed)),
|
|
|
|
Err(_) => Err(Error::new(ErrorCode::SelfTestFailed)),
|
2020-08-31 17:43:15 +08:00
|
|
|
}
|
|
|
|
}
|
2020-08-28 15:48:13 +08:00
|
|
|
}
|