kcan 0.1.2

CAN controller primitives for actuator and motor control.
Documentation
#![cfg(feature = "socketcan")]

use std::env;
use std::time::Duration;

use kcan::{CanMonitor, MotorConfig, MotorController, MotorModel, Result, SocketCanBus};

const HARDWARE_ENV: &str = "KCAN_ENABLE_HARDWARE_TESTS";
const WRITE_ENV: &str = "KCAN_ENABLE_WRITE_TESTS";
const INTERFACE_ENV: &str = "KCAN_CAN_IFACE";
const MOTOR_ID_ENV: &str = "KCAN_MOTOR_ID";
const MOTOR_MODEL_ENV: &str = "KCAN_MOTOR_MODEL";

#[test]
#[ignore = "requires live SocketCAN hardware; set KCAN_ENABLE_HARDWARE_TESTS=1"]
fn hardware_socketcan_monitor_polls_without_transport_error() -> Result<()> {
    if !hardware_tests_enabled() {
        eprintln!("skipping hardware test; set {HARDWARE_ENV}=1 to enable");
        return Ok(());
    }

    let mut bus = SocketCanBus::open(&interface())?;
    let mut monitor = CanMonitor::new(64);
    monitor.poll_available(&mut bus, Duration::from_millis(250), 32)?;
    Ok(())
}

#[test]
#[ignore = "requires live CubeMars hardware; set KCAN_ENABLE_HARDWARE_TESTS=1 and KCAN_ENABLE_WRITE_TESTS=1"]
fn hardware_cubemars_neutral_mit_write_is_double_gated() -> Result<()> {
    if !hardware_tests_enabled() {
        eprintln!("skipping hardware test; set {HARDWARE_ENV}=1 to enable");
        return Ok(());
    }
    if env::var(WRITE_ENV).as_deref() != Ok("1") {
        eprintln!("skipping write test; set {WRITE_ENV}=1 after bench safety checks");
        return Ok(());
    }

    let config = MotorConfig {
        motor_id: motor_id(),
        model: motor_model(),
        ..MotorConfig::default()
    };
    let mut motor = MotorController::new(SocketCanBus::open(&interface())?, config)?;

    motor.enable_mit_mode()?;
    motor.stop()
}

fn hardware_tests_enabled() -> bool {
    env::var(HARDWARE_ENV).as_deref() == Ok("1")
}

fn interface() -> String {
    env::var(INTERFACE_ENV).unwrap_or_else(|_| "can0".to_owned())
}

fn motor_id() -> u8 {
    let value = env::var(MOTOR_ID_ENV).unwrap_or_else(|_| "0x03".to_owned());
    parse_u8(&value).unwrap_or_else(|| {
        panic!("{MOTOR_ID_ENV} must be decimal 0..255 or hex 0x00..0xFF, got {value:?}")
    })
}

fn parse_u8(value: &str) -> Option<u8> {
    let value = value.trim();
    if let Some(hex) = value
        .strip_prefix("0x")
        .or_else(|| value.strip_prefix("0X"))
    {
        u8::from_str_radix(hex, 16).ok()
    } else {
        value.parse::<u8>().ok()
    }
}

fn motor_model() -> MotorModel {
    let value = env::var(MOTOR_MODEL_ENV).unwrap_or_else(|_| "ak60-6".to_owned());
    MotorModel::from_name(&value)
        .unwrap_or_else(|| panic!("{MOTOR_MODEL_ENV} is not a supported CubeMars model: {value:?}"))
}