From ab4749118c6913df0b7b26a809e663304828e942 Mon Sep 17 00:00:00 2001 From: occheung Date: Fri, 25 Sep 2020 10:59:15 +0800 Subject: [PATCH] src: purge scpi --- src/scpi.rs | 510 --------------------------------------------- src/translation.rs | 56 ----- 2 files changed, 566 deletions(-) delete mode 100644 src/scpi.rs delete mode 100644 src/translation.rs diff --git a/src/scpi.rs b/src/scpi.rs deleted file mode 100644 index 9fa2a8a..0000000 --- a/src/scpi.rs +++ /dev/null @@ -1,510 +0,0 @@ -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}; -use core::str; -use scpi::{ - nquery, - qonly, -}; -use scpi::suffix::{Amplitude, Db}; -use uom::si::frequency::{gigahertz, hertz, kilohertz, megahertz, Frequency}; -use uom::si::angle::{degree, gon, minute as aminute, radian, revolution, Angle}; -use uom::si::{f32, f64}; - -use embedded_hal::blocking::spi::Transfer; - -use crate::{ - Urukul, - UrukulTraits, - Error as UrukulError, - ClockSource, -}; - -use log::{trace, debug, info, warn}; - -#[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 - ([$header_name: expr] => {$($($rest: tt)=>*),*}) => { - Node { - name: str::as_bytes($header_name), - handler: None, - sub: &[ - $( - recursive_scpi_tree!($($rest)=>*), - )* - ], - optional: true, - } - }; - - // Handle non-optional header with sub-commands - ($header_name: expr => {$($($rest: tt)=>*),*}) => { - Node { - name: str::as_bytes($header_name), - handler: None, - sub: &[ - $( - recursive_scpi_tree!($($rest)=>*), - )* - ], - optional: false, - } - }; -} - -#[macro_export] -macro_rules! scpi_root { - ($($($node: tt)=>*),*) => { - &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)=>*), - )* - ] - } - }; -} - -#[macro_export] -macro_rules! scpi_tree { - () => { - scpi_root!( - "CHANNEL0" => { - "SWitch" => Channel0SwitchCommand, - "ATTenuation" => Channel0AttenuationCommand, - "SYSCLOCK" => Channel0SystemClockCommand, - "PROFILE0" => { - "SINGLEtone" => { - "FREQuency" => Channel0Profile0SingletoneFrequencyCommand, - "PHASE" => Channel0Profile0SingletonePhaseCommand, - "AMPlitude" => Channel0Profile0SingletoneAmplitudeCommand, - ["Setup"] => Channel0Profile0SingletoneCommand - } - } - }, - "CHANNEL1" => { - "SWitch" => Channel1SwitchCommand, - "ATTenuation" => Channel1AttenuationCommand - }, - "CHANNEL2" => { - "SWitch" => Channel2SwitchCommand, - "ATTenuation" => Channel2AttenuationCommand - }, - "CHANNEL3" => { - "SWitch" => Channel3SwitchCommand, - "ATTenuation" => Channel3AttenuationCommand - }, - "CLOCK" => { - "SOURCE" => ClockSourceCommand, - "DIVision" => ClockDivisionCommand - }, - "PROFILE" => ProfileCommand, - ["EXAMple"] => { - "HELLO" => { - "WORLD" => HelloWorldCommand - } - } - ); - }; -} - -pub struct HelloWorldCommand {} -impl Command for HelloWorldCommand { - qonly!(); - - fn query( - &self, - _context: &mut Context, - _args: &mut Tokenizer, - response: &mut ResponseUnit, - ) -> Result<()> { - response.data(b"Hello world" as &[u8]).finish() - } -} - -pub struct Channel0SwitchCommand {} -pub struct Channel1SwitchCommand {} -pub struct Channel2SwitchCommand {} -pub struct Channel3SwitchCommand {} - -macro_rules! impl_channel_switch_command { - ($($channel: literal => $command_struct: ty),*) => { - $( - impl Command for $command_struct { - nquery!(); - - fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { - let next_state: bool = args.next_data(true)? - .map_or( - context.device.get_channel_switch_status($channel) - .map(|current| !current) - .map_err(|_| Error::new(ErrorCode::HardwareError)), - |token| token.try_into() - )?; - context.device.set_channel_switch($channel, next_state).map_err(|_| Error::new(ErrorCode::HardwareError)) - } - } - )* - }; -} - -impl_channel_switch_command!( - 0 => Channel0SwitchCommand, - 1 => Channel1SwitchCommand, - 2 => Channel2SwitchCommand, - 3 => Channel3SwitchCommand -); - -pub struct ClockSourceCommand {} -pub struct ClockDivisionCommand {} -pub struct Channel0SystemClockCommand {} -pub struct ProfileCommand {} -pub struct Channel0Profile0SingletoneCommand {} -pub struct Channel0Profile0SingletoneFrequencyCommand {} -pub struct Channel0Profile0SingletonePhaseCommand {} -pub struct Channel0Profile0SingletoneAmplitudeCommand {} - -// Handle CLOCK:SOURCE command, setup the proper source for the system clock -// Leave clock division to CLOCK:DIVision command -impl Command for ClockSourceCommand { - nquery!(); - - fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { - - // next_data() fucntion call can never return CharacterProgramData, could be an oversight - let s: &[u8] = match args.next_data(false)? { - Some(Token::CharacterProgramData(s)) => s, - _ => return Err(ErrorCode::IllegalParameterValue.into()), - }; - - let s_str: &str = str::from_utf8(s) - .map_err(|_| ErrorCode::CharacterDataError)?; - - let frequency: f64::Frequency = args.next_data(true)? - .map_or(Ok(f64::Frequency::new::(0.0)), |t| { - t.numeric(|s| match s { - NumericValues::Default => Ok(f64::Frequency::new::(0.0)), - _ => Err(ErrorCode::IllegalParameterValue.into()), - }) - })?; - trace!("Received master clock frequency: {:?}", frequency); - - let clock_source = match s_str { - source if source.eq_ignore_ascii_case("OSC") => { - // If clock source is OSC, it must be 100MHz (not configurable) - if frequency.get::() != 100.0 { - warn!("Clock selection failed! OSC must be 100 MHz"); - return Err(ErrorCode::IllegalParameterValue.into()); - } - ClockSource::OSC - }, - source if source.eq_ignore_ascii_case("MMCX") => { - // TODO: Implement frequency check for MMCX - ClockSource::MMCX - }, - source if source.eq_ignore_ascii_case("SMA") => { - // TODO: Implement frequency check for SMA - ClockSource::SMA - }, - _ => { - warn!("Clock selection failed! Argument error!"); - return Err(ErrorCode::IllegalParameterValue.into()); - }, - }; - - trace!("Changing clock source to {:?} at {:?}", clock_source, frequency); - context.device.set_clock_source(clock_source, frequency.get::()) - .map_err(|_| Error::new(ErrorCode::HardwareError)) - } -} - -impl Command for ClockDivisionCommand { - nquery!(); - - fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { - let div :f32 = args.next_data(false)? - .map_or(Err(Error::new(ErrorCode::IllegalParameterValue)), - |token| token.try_into())?; - trace!("Received master clock division factor: {}", div); - if div == 1.0 || div == 2.0 || div == 4.0 { - debug!("Set master clock division as {}", div); - context.device.set_clock_division(div as u8) - .map_err(|_| Error::new(ErrorCode::HardwareError)) - } else { - Err(Error::new(ErrorCode::IllegalParameterValue)) - } - } -} - -impl Command for Channel0SystemClockCommand { - nquery!(); - - // Param: - // The exact method of generating this frequency is auto-decided - // The process is delegated to individual DDS chip - fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { - - let frequency: f64::Frequency = args.next_data(true)? - .map_or(Ok(f64::Frequency::new::(0.0)), |t| { - t.numeric(|s| match s { - NumericValues::Default => Ok(f64::Frequency::new::(0.0)), - _ => Err(ErrorCode::IllegalParameterValue.into()), - }) - })?; - trace!("Received channel 0 system clock frequency: {:?}", frequency); - - // Setup sys_clk through urukul interface - context.device.set_channel_sys_clk(0, frequency.get::()).map_err(|_| Error::new(ErrorCode::IllegalParameterValue)) - } -} - -pub struct Channel0AttenuationCommand {} -pub struct Channel1AttenuationCommand {} -pub struct Channel2AttenuationCommand {} -pub struct Channel3AttenuationCommand {} - -macro_rules! impl_channel_attenuation_command { - ($($channel: literal => $command_struct: ty),*) => { - $( - impl Command for $command_struct { - nquery!(); - - fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { - let attenuation: f32 = args.next_data(false)? - .map_or(Err(Error::new(ErrorCode::IllegalParameterValue)), - |token| token.try_into())?; - trace!("Received channel {} attenuation input: {}", $channel, attenuation); - context.device.set_channel_attenuation($channel, attenuation) - .map_err(|_| Error::new(ErrorCode::HardwareError)) - } - } - )* - }; -} - -impl_channel_attenuation_command!( - 0 => Channel0AttenuationCommand, - 1 => Channel1AttenuationCommand, - 2 => Channel2AttenuationCommand, - 3 => Channel3AttenuationCommand -); - -impl Command for ProfileCommand { - nquery!(); - - fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { - let profile :f32 = args.next_data(false)? - .map_or(Err(Error::new(ErrorCode::IllegalParameterValue)), - |token| token.try_into())?; - if ((profile as u8) as f32) != profile { - return Err(Error::new(ErrorCode::IllegalParameterValue)); - } - - trace!("Selected Profile :{}", profile); - let profile = profile as u8; - if profile >= 8 { - Err(Error::new(ErrorCode::IllegalParameterValue)) - } else { - context.device.set_profile(profile) - .map_err(|_| Error::new(ErrorCode::HardwareError)) - } - } -} - -impl Command for Channel0Profile0SingletoneCommand { - nquery!(); - - // Params: frequency, phase, amplitude (all mandatory) - fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { - - // Read output frequency - let frequency: f64::Frequency = args.next_data(false)? - .map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| { - t.numeric(|s| match s { - NumericValues::Default => Ok(f64::Frequency::new::(0.0)), - _ => Err(ErrorCode::IllegalParameterValue.into()), - }) - })?; - trace!("Received channel 0 profile 0 output single tone frequency: {:?}", frequency); - // Handle negative frequency - if frequency.get::() < 0.0 { - return Err(ErrorCode::DataOutOfRange.into()); - } - - // Read phase offset - let phase: f64::Angle = args.next_data(false)? - .map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| { - t.numeric( - |s| match s { - NumericValues::Default => Ok(f64::Angle::new::(0.0)), - _ => Err(ErrorCode::IllegalParameterValue.into()), - }) - })?; - trace!("Received channel 0 profile 0 output single tone phase offset: {:?}", phase); - // Handle out-of-bound phase offset - if phase.get::() < 0.0 || phase.get::() >= 360.0 { - return Err(ErrorCode::DataOutOfRange.into()); - } - - // Read amplitude offset - let amplitude: f64 = args.next_data(false)? - .map_or(Err(Error::new(ErrorCode::MissingParameter)), - |token| token.try_into())?; - trace!("Received channel 0 profile 0 output single tone amplitude offset: {:?}", amplitude); - // Handle out-of-bound phase offset - if amplitude < 0.0 || amplitude > 1.0 { - return Err(ErrorCode::DataOutOfRange.into()); - } - - trace!("Set up a single tone on channel 0, profile 0"); - context.device.set_channel_single_tone_profile(0, 0, frequency.get::(), phase.get::(), amplitude) - .map_err(|_| Error::new(ErrorCode::HardwareError)) - } -} - -impl Command for Channel0Profile0SingletoneFrequencyCommand { - // TODO: Implement query for publishing - nquery!(); - - // Param: frequency - fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { - - // Read output frequency - let frequency: f64::Frequency = args.next_data(false)? - .map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| { - t.numeric(|s| match s { - NumericValues::Default => Ok(f64::Frequency::new::(0.0)), - _ => Err(ErrorCode::IllegalParameterValue.into()), - }) - })?; - trace!("Received channel 0 profile 0 output single tone frequency: {:?}", frequency); - // Handle negative frequency - if frequency.get::() < 0.0 { - return Err(ErrorCode::DataOutOfRange.into()); - } - - context.device.set_channel_single_tone_profile_frequency(0, 0, frequency.get::()) - .map_err(|_| Error::new(ErrorCode::HardwareError)) - } -} - -impl Command for Channel0Profile0SingletonePhaseCommand { - // TODO: Implement query for publishing - nquery!(); - - // Param: frequency - fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { - - // Read phase offset - let phase: f64::Angle = args.next_data(false)? - .map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| { - t.numeric( - |s| match s { - NumericValues::Default => Ok(f64::Angle::new::(0.0)), - _ => Err(ErrorCode::IllegalParameterValue.into()), - }) - })?; - trace!("Received channel 0 profile 0 output single tone phase offset: {:?}", phase); - // Handle out-of-bound phase offset - if phase.get::() < 0.0 || phase.get::() >= 360.0 { - return Err(ErrorCode::DataOutOfRange.into()); - } - context.device.set_channel_single_tone_profile_phase(0, 0, phase.get::()) - .map_err(|_| Error::new(ErrorCode::HardwareError)) - } -} - -impl Command for Channel0Profile0SingletoneAmplitudeCommand { - // TODO: Implement query for publishing - nquery!(); - - // Param: frequency - fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { - - // Read amplitude offset - let amplitude: f64 = args.next_data(false)? - .map_or(Err(Error::new(ErrorCode::MissingParameter)), - |token| token.try_into())?; - trace!("Received channel 0 profile 0 output single tone amplitude offset: {:?}", amplitude); - // Handle out-of-bound phase offset - if amplitude < 0.0 || amplitude > 1.0 { - return Err(ErrorCode::DataOutOfRange.into()); - } - context.device.set_channel_single_tone_profile_amplitude(0, 0, amplitude) - .map_err(|_| Error::new(ErrorCode::HardwareError)) - } -} - -/* - * Implement "Device" trait from SCPI - * TODO: Implement mandatory commands - */ -impl Device for Urukul -where - SPI: Transfer -{ - fn cls(&mut self) -> Result<()> { - Ok(()) - } - - fn rst(&mut self) -> Result<()> { - match self.reset() { - Ok(_) => Ok(()), - Err(_) => Err(Error::new(ErrorCode::HardwareError)) - } - } - - fn tst(&mut self) -> Result<()> { - match self.test() { - Ok(0) => Ok(()), - Ok(_) => Err(Error::new(ErrorCode::SelfTestFailed)), - Err(_) => Err(Error::new(ErrorCode::SelfTestFailed)), - } - } -} diff --git a/src/translation.rs b/src/translation.rs deleted file mode 100644 index 42d50e3..0000000 --- a/src/translation.rs +++ /dev/null @@ -1,56 +0,0 @@ -use scpi::prelude::*; -use scpi::Context; -use scpi::error::Result; -use log::{trace, info}; -use arrayvec::{ArrayVec}; - -pub trait MqttScpiTranslator { - // Unwrap an MQTT publish message into SCPI compatible command - // The command part/ MQTT message must follow SCPI standard for parameter formatting - fn run_with_mqtt(&mut self, topic: &str, args: &str, response: &mut FMT) -> Result<()>; -} - -impl<'a, T: Device> MqttScpiTranslator for Context<'a, T> { - fn run_with_mqtt(&mut self, topic: &str, args: &str, response: &mut FMT) -> Result<()> - where - FMT: Formatter, - { - if !topic.starts_with("Urukul/Control") { - info!("Received a publish, but not for control! Topic: {}", topic); - return Ok(()); - } - - // let command_topic = topic.strip_prefix("Urukul/Control") - // .unwrap_or(""); - - // Create a fixed-size buffer to handle slice operation - let mut buffer = ArrayVec::<[u8; 1024]>::new(); - - // // Copy MQTT topic, convert it into SCPI header format - // for i in command_topic.chars() { - // if i == '/' { - // // The topic separator is colon(':') in SCPI, and slash('/') in MQTT - // buffer.try_push(b':') - // .map_err(|_| ErrorCode::OutOfMemory)?; - // } else { - // buffer.try_push(i as u8) - // .map_err(|_| ErrorCode::OutOfMemory)?; - // } - // } - - // // Place a space bar between header and parameter - // buffer.try_push(b' ') - // .map_err(|_| ErrorCode::OutOfMemory)?; - - // Copy the arguments into the buffer - for i in args.chars() { - buffer.try_push(i as u8) - .map_err(|_| ErrorCode::OutOfMemory)?; - } - - // Pass the message to SCPI processing unit - trace!("Translated MQTT message into SCPI. Translated command: {}", - core::str::from_utf8(buffer.as_slice()).unwrap()); - self.run(buffer.as_slice(), response) - } -} \ No newline at end of file