microscpi 0.6.0

A Rust library for creating SCPI interfaces.
Documentation
use microscpi::{
    self as scpi, ErrorCommands, ErrorQueue, Interface, StandardCommands, StaticErrorQueue,
    StatusCommands, StatusRegisters,
};

#[derive(Debug, PartialEq)]
pub enum TestResult {
    ResetOk,
    IdnOk,
    TestA,
    TestAQ,
    Arbitrary(Vec<u8>),
}

pub struct TestInterface {
    errors: StaticErrorQueue<10>,
    result: Option<TestResult>,
    registers: StatusRegisters,
}

impl StatusCommands for TestInterface {
    fn status_registers(&mut self) -> &mut StatusRegisters {
        &mut self.registers
    }
}

impl ErrorCommands for TestInterface {
    fn error_queue(&mut self) -> &mut impl ErrorQueue {
        &mut self.errors
    }
}

impl StandardCommands for TestInterface {}

#[scpi::interface(StandardCommands, ErrorCommands, StatusCommands)]
impl TestInterface {
    #[scpi(cmd = "*RST")]
    pub async fn rst(&mut self) -> Result<(), scpi::Error> {
        self.result = Some(TestResult::ResetOk);
        Ok(())
    }

    #[scpi(cmd = "*IDN?")]
    pub async fn idn(&mut self) -> Result<&str, scpi::Error> {
        self.result = Some(TestResult::IdnOk);
        Ok("MICROSCPI,TEST,1,1.0")
    }

    #[scpi(cmd = "VALue:STRing?")]
    pub async fn value_str(&mut self) -> Result<&str, scpi::Error> {
        Ok("Hello World")
    }

    #[scpi(cmd = "[SYSTem]:TeST:A")]
    pub async fn system_test_a(&mut self) -> Result<(), scpi::Error> {
        self.result = Some(TestResult::TestA);
        Ok(())
    }

    #[scpi(cmd = "[SYSTem]:TeST:A?")]
    pub async fn system_test_aq(&mut self) -> Result<(), scpi::Error> {
        self.result = Some(TestResult::TestAQ);
        Ok(())
    }

    #[scpi(cmd = "MATH:OPeration:MULTiply?")]
    pub async fn math_multiply(&mut self, a: u64, b: u64) -> Result<u64, scpi::Error> {
        Ok(a * b)
    }

    #[scpi(cmd = "MATH:OPeration:MULTiplyFloat?")]
    pub async fn math_multiply_float(&mut self, a: f64, b: f64) -> Result<f64, scpi::Error> {
        Ok(a * b)
    }

    #[scpi(cmd = "ARGument:ARBitrary")]
    pub async fn argument_arbitrary(&mut self, _value: &'_ [u8]) -> Result<(), scpi::Error> {
        self.result = Some(TestResult::Arbitrary(_value.into()));
        Ok(())
    }
}

fn setup() -> (TestInterface, Vec<u8>) {
    let interface = TestInterface {
        errors: StaticErrorQueue::new(),
        registers: StatusRegisters::default(),
        result: None,
    };
    (interface, Vec::new())
}

#[tokio::test]
async fn test_idn() {
    let (mut interface, mut output) = setup();
    interface.run(b"*IDN?\n", &mut output).await;
    assert_eq!(interface.result, Some(TestResult::IdnOk));
}

#[tokio::test]
async fn test_rst() {
    let (mut interface, mut output) = setup();
    interface.run(b"*RST\n", &mut output).await;
    assert_eq!(interface.result, Some(TestResult::ResetOk));
}

#[tokio::test]
async fn test_a_short() {
    let (mut interface, mut output) = setup();
    interface.run(b"TST:A\n", &mut output).await;
    assert_eq!(interface.result, Some(TestResult::TestA));
}

#[tokio::test]
async fn test_a_long() {
    let (mut interface, mut output) = setup();
    interface.run(b"SYSTEM:TEST:A\r\n", &mut output).await;
    assert_eq!(interface.result, Some(TestResult::TestA));
}

#[tokio::test]
async fn test_system_test_aq() {
    let (mut interface, mut output) = setup();
    interface.run(b"SYST:TEST:A?\n", &mut output).await;
    assert_eq!(interface.result, Some(TestResult::TestAQ));
}

#[tokio::test]
async fn test_value_string() {
    let (mut interface, mut output) = setup();

    interface.run(b"VAL:STR?\n", &mut output).await;

    assert_eq!(output, b"\"Hello World\"\n");
}

#[tokio::test]
async fn test_value_arbitrary() {
    let (mut interface, mut output) = setup();

    let input = [
        65, 82, 71, 58, 65, 82, 66, 32, 35, 50, 51, 50, 57, 14, 100, 57, 14, 116, 57, 14, 132, 57,
        14, 147, 57, 14, 163, 57, 14, 179, 57, 14, 195, 57, 14, 210, 57, 14, 227, 57, 14, 243, 57,
        15, 10, 83, 89, 83, 84, 58, 69, 82, 82, 58, 78, 69, 88, 84, 63, 10,
    ];

    let remaining = interface.run(&input, &mut output).await;

    assert_eq!(interface.errors.pop_error(), None);
    assert_eq!(remaining, &[]);

    assert_eq!(
        interface.result,
        Some(TestResult::Arbitrary(Vec::from(&[
            57, 14, 100, 57, 14, 116, 57, 14, 132, 57, 14, 147, 57, 14, 163, 57, 14, 179, 57, 14,
            195, 57, 14, 210, 57, 14, 227, 57, 14, 243, 57, 15
        ])))
    );

    assert_eq!(output, b"0,\"\"\n");
}

#[tokio::test]
async fn test_terminators() {
    let (mut interface, mut output) = setup();

    assert_eq!(interface.run(b"*IDN?\n", &mut output).await, &[]);
    assert_eq!(interface.run(b"*IDN?\r\n", &mut output).await, &[]);
    assert_eq!(interface.run(b"*IDN?\n\r", &mut output).await, &[]);
}

#[tokio::test]
async fn test_invalid_command() {
    let (mut interface, mut output) = setup();

    interface.run(b"*IDN\n", &mut output).await;
    assert_eq!(
        interface.errors.pop_error(),
        Some(scpi::Error::UndefinedHeader)
    );
    assert_eq!(interface.errors.pop_error(), None);

    interface.run(b"FOO\n", &mut output).await;
    assert_eq!(
        interface.errors.pop_error(),
        Some(scpi::Error::UndefinedHeader)
    );
    assert_eq!(interface.errors.pop_error(), None);

    interface.run(b"FOO:BAR\n", &mut output).await;
    assert_eq!(
        interface.errors.pop_error(),
        Some(scpi::Error::UndefinedHeader)
    );
    assert_eq!(interface.errors.pop_error(), None);

    interface.run(b"SYST:FOO\n", &mut output).await;
    assert_eq!(
        interface.errors.pop_error(),
        Some(scpi::Error::UndefinedHeader)
    );
    assert_eq!(interface.errors.pop_error(), None);

    interface.run(b"INVALID:CMD;*STB?\n", &mut output).await;
    assert_eq!(output, b"36\n");
    assert_eq!(
        interface.errors.pop_error(),
        Some(scpi::Error::UndefinedHeader)
    );
    assert_eq!(interface.errors.pop_error(), None);
}

#[tokio::test]
async fn test_invalid_character() {
    let (mut interface, mut output) = setup();

    interface.run("*IDN!\n".as_bytes(), &mut output).await;
    assert_eq!(
        interface.errors.pop_error(),
        Some(scpi::Error::InvalidCharacter)
    );
}
#[tokio::test]
async fn test_math_multiply() {
    let (mut interface, mut output) = setup();
    interface.run(b"MATH:OP:MULT? 7,6\n", &mut output).await;
    assert_eq!(output, b"42\n");
}

#[tokio::test]
async fn test_math_multiply_float() {
    let (mut interface, mut output) = setup();
    interface
        .run(b"MATH:OP:MULTF? 23.42,42.23\n", &mut output)
        .await;
    assert_eq!(output, b"989.0266\n");
}

#[tokio::test]
async fn test_math_multiply_hexadecimal() {
    let (mut interface, mut output) = setup();
    interface
        .run(b"MATH:OP:MULT? #H7B,#Q710\n", &mut output)
        .await;
    assert_eq!(output, b"56088\n");
}

#[tokio::test]
async fn test_invalid_arguments() {
    let (mut interface, mut output) = setup();

    interface.run(b"SYSTEM:TEST:A 123 456\n", &mut output).await;
    assert_eq!(
        interface.errors.pop_error(),
        Some(scpi::Error::InvalidCharacter)
    );

    interface
        .run(b"SYSTEM:TEST:A 123,,456\n", &mut output)
        .await;
    assert_eq!(
        interface.errors.pop_error(),
        Some(scpi::Error::InvalidCharacter)
    );

    interface.run(b"SYSTEM:TEST:A ,123\n", &mut output).await;
    assert_eq!(
        interface.errors.pop_error(),
        Some(scpi::Error::InvalidCharacter)
    );

    interface.run(b"SYSTEM:TEST:A,123\n", &mut output).await;
    assert_eq!(
        interface.errors.pop_error(),
        Some(scpi::Error::InvalidCharacter)
    );

    assert_eq!(interface.errors.pop_error(), None);
}

#[tokio::test]
async fn test_next_error() {
    let (mut interface, mut output) = setup();

    interface.errors.push_error(scpi::Error::SystemError);

    interface.run(b"SYST:ERR:NEXT?\n", &mut output).await;

    assert_eq!(output, b"-310,\"System error\"\n");

    output.clear();

    interface.run(b"SYST:ERR:NEXT?\n", &mut output).await;

    assert_eq!(output, b"0,\"\"\n");
}

#[tokio::test]
async fn test_value_string_with_whitespace() {
    let (mut interface, mut output) = setup();
    interface.run(b"  VAL:STR?  \n", &mut output).await;
    assert_eq!(output, b"\"Hello World\"\n");
}

#[tokio::test]
async fn test_multiple_commands() {
    let (mut interface, mut output) = setup();
    interface.run(b"*RST\n*IDN?\n", &mut output).await;
    assert_eq!(interface.result, Some(TestResult::IdnOk));
    assert_eq!(output, b"\"MICROSCPI,TEST,1,1.0\"\n");
}

#[tokio::test]
async fn test_empty_input() {
    let (mut interface, mut output) = setup();
    let remaining = interface.run(b"", &mut output).await;
    assert_eq!(remaining, &[]);

    let remaining = interface.run(b"\n", &mut output).await;
    assert_eq!(remaining, &[]);

    let remaining = interface.run(b" \n", &mut output).await;
    assert_eq!(remaining, &[]);

    let remaining = interface.run(b"  \n  \n\n  ", &mut output).await;
    assert_eq!(remaining, &[]);
}

#[tokio::test]
async fn test_event_status_register() {
    let (mut interface, mut output) = setup();
    interface.run(b"*ESR?\n", &mut output).await;
    assert_eq!(output, b"128\n");

    // All enable status bits should be set by default.
    output.clear();
    interface.run(b"*ESE?\n", &mut output).await;
    assert_eq!(output, b"255\n");

    // After clearing, no status bits should be set.
    interface.run(b"*CLS\n", &mut output).await;
    assert!(interface.registers.event_status.is_empty());

    output.clear();
    interface.run(b"*ESR?\n", &mut output).await;
    assert_eq!(output, b"0\n");

    interface.run(b"*CLS\n", &mut output).await;
    assert!(interface.registers.event_status.is_empty());

    output.clear();
    interface.run(b"*OPC?\n", &mut output).await;
    assert_eq!(output, b"1\n");
}

#[tokio::test]
async fn test_status_byte_register() {
    let (mut interface, mut output) = setup();

    // All enable status bits should be set by default.
    interface.run(b"*SRE?\n", &mut output).await;
    assert_eq!(output, b"252\n");

    // After startup the event status bit should be set.
    output.clear();
    interface.run(b"*STB?\n", &mut output).await;
    assert_eq!(output, b"32\n");

    // After clearing, no status bits should be set.
    interface.run(b"*CLS\n", &mut output).await;
    output.clear();
    interface.run(b"*STB?\n", &mut output).await;
    assert_eq!(output, b"0\n");

    interface.errors.push_error(scpi::Error::SystemError);

    output.clear();
    interface.run(b"*STB?\n", &mut output).await;
    assert_eq!(output, b"4\n");

    // After clearing, no status bits should be set.
    interface.run(b"*CLS\n", &mut output).await;
    output.clear();
    interface.run(b"*STB?\n", &mut output).await;
    assert_eq!(output, b"0\n");

    // And the error queue should be empty.
    assert_eq!(interface.errors.error_count(), 0);
}