kcan 0.3.0

CAN controller primitives for actuator and motor control.
Documentation
use std::process::{Command, Output};

fn run(args: &[&str]) -> Output {
    Command::new(env!("CARGO_BIN_EXE_kcan"))
        .args(args)
        .output()
        .expect("kcan binary should run")
}

fn stdout(output: Output) -> String {
    assert!(output.status.success(), "status={:?}", output.status.code());
    String::from_utf8(output.stdout).expect("stdout should be UTF-8")
}

#[test]
fn cli_help_mentions_main_entry_points() {
    let output = stdout(run(&["help"]));

    assert!(output.contains("Knott Dynamics"));
    assert!(output.contains("Scrin"));
    assert!(output.contains("Aisling"));
    assert!(output.contains("kcan models"));
}

#[test]
fn cli_lists_supported_models() {
    let output = stdout(run(&["models"]));

    assert!(output.contains("AK60-39"));
    assert!(output.contains("AK100-33"));
    assert!(output.contains("RS-06"));
    assert!(output.contains("K00"));
    assert!(output.contains("K01"));
}

#[test]
fn cli_builds_cubemars_frames() {
    let mit = stdout(run(&["cubemars", "mit", "ak60-6", "0x03"]));
    assert!(mit.contains("extended 0x803 len=8"));

    let ak60_39 = stdout(run(&["cubemars", "mit", "ak60-39", "0x03"]));
    assert!(ak60_39.contains("extended 0x803 len=8 data=00 00 00 7F FF 7F F7 FF"));

    let direct = stdout(run(&["cubemars", "direct", "0x03", "velocity", "1000"]));
    assert!(direct.contains("extended 0x303 len=4 data=00 00 03 E8"));

    let disable = stdout(run(&["cubemars", "direct", "0x03", "disable"]));
    assert!(disable.contains("extended 0xF03 len=0 data="));

    let feedback = stdout(run(&[
        "cubemars",
        "direct",
        "0x03",
        "feedback-config",
        "position-32bit",
        "single-turn",
    ]));
    assert!(feedback.contains("extended 0x1003 len=8 data=00 00 00 00 00 00 C0 00"));

    let clear = stdout(run(&[
        "cubemars",
        "direct",
        "0x03",
        "feedback-config",
        "clear",
    ]));
    assert!(clear.contains("extended 0x1003 len=8 data=00 00 00 00 00 00 00 00"));
}

#[test]
fn cli_builds_robstride_frames() {
    let status = stdout(run(&["robstride", "status", "rs01", "1"]));
    assert!(status.contains("extended 0x501 len=8"));

    let speed = stdout(run(&["robstride", "speed", "rs03", "3", "1.5", "12.0"]));
    assert!(speed.contains("extended 0x403 len=8"));
}

#[test]
fn cli_builds_knott_dynamics_frames() {
    let nmt = stdout(run(&["knott", "nmt", "broadcast", "disabled", "idle"]));
    assert!(nmt.contains("standard 0x0 len=2 data=01 00"));

    let position = stdout(run(&["knott", "position-velocity", "1", "1.0", "-2.0"]));
    assert!(position.contains("standard 0x301 len=8 data=00 00 80 3F 00 00 00 C0"));

    let read = stdout(run(&["knott", "read", "1", "firmware_version"]));
    assert!(read.contains("standard 0x601 len=4 data=40 04 00 00"));

    let write = stdout(run(&["knott", "write", "1", "idle", "mode", "torque"]));
    assert!(write.contains("standard 0x601 len=8 data=20 10 00 00 11 00 00 00"));

    let heartbeat = stdout(run(&["knott", "heartbeat", "1"]));
    assert!(heartbeat.contains("standard 0x701 len=0 data="));

    let disable = stdout(run(&["knott", "disable", "1"]));
    assert!(disable.contains("standard 0x0 len=2 data=00 01"));
}

#[test]
fn cli_rejects_bad_commands() {
    let output = run(&["cubemars", "direct", "0x03", "velocity"]);

    assert!(!output.status.success());
    let stderr = String::from_utf8(output.stderr).expect("stderr should be UTF-8");
    assert!(stderr.contains("error:"));

    let feedback = run(&["cubemars", "direct", "0x03", "feedback-config"]);
    assert!(!feedback.status.success());

    let mixed_clear = run(&[
        "cubemars",
        "direct",
        "0x03",
        "feedback-config",
        "clear",
        "position-32bit",
    ]);
    assert!(!mixed_clear.status.success());
    assert!(
        String::from_utf8(mixed_clear.stderr)
            .unwrap()
            .contains("clear cannot be combined")
    );

    let calibration = run(&["knott", "nmt", "broadcast", "idle", "calibration"]);
    assert!(!calibration.status.success());
    assert!(
        String::from_utf8(calibration.stderr)
            .unwrap()
            .contains("cannot be requested over CAN")
    );

    let invalid_transition = run(&["knott", "nmt", "1", "disabled", "position"]);
    assert!(!invalid_transition.status.success());
    assert!(
        String::from_utf8(invalid_transition.stderr)
            .unwrap()
            .contains("invalid Knott Dynamics mode transition")
    );

    let unsafe_write = run(&["knott", "write", "1", "position", "motor_pole_pairs", "14"]);
    assert!(!unsafe_write.status.success());
    assert!(
        String::from_utf8(unsafe_write.stderr)
            .unwrap()
            .contains("requires disabled or idle")
    );
}