openrr-remote 0.1.0

Remote execution support for arci
Documentation
use std::{
    net::SocketAddr,
    sync::{
        atomic::{AtomicU16, Ordering},
        Arc,
    },
    time::{Duration, SystemTime},
};

use anyhow::Result;
use arci::{
    gamepad::GamepadEvent, BaseVelocity, DummyGamepad, DummyJointTrajectoryClient,
    DummyLocalization, DummyMoveBase, DummyNavigation, DummySpeaker, DummyTransformResolver,
    Gamepad, Isometry2, JointTrajectoryClient, Localization, MoveBase, Navigation, Speaker,
    TrajectoryPoint, TransformResolver, Vector2,
};
use assert_approx_eq::assert_approx_eq;
use openrr_remote::{
    RemoteGamepadReceiver, RemoteGamepadSender, RemoteJointTrajectoryClientReceiver,
    RemoteJointTrajectoryClientSender, RemoteLocalizationReceiver, RemoteLocalizationSender,
    RemoteMoveBaseReceiver, RemoteMoveBaseSender, RemoteNavigationReceiver, RemoteNavigationSender,
    RemoteSpeakerReceiver, RemoteSpeakerSender, RemoteTransformResolverReceiver,
    RemoteTransformResolverSender,
};

fn endpoint() -> (SocketAddr, String) {
    static PORT: AtomicU16 = AtomicU16::new(50061);
    let addr = format!("[::1]:{}", PORT.fetch_add(1, Ordering::SeqCst));
    (addr.parse().unwrap(), format!("http://{addr}"))
}

#[tokio::test(flavor = "multi_thread")]
async fn joint_trajectory_client() -> Result<()> {
    let (addr, endpoint) = endpoint();

    // Launch server
    {
        let client =
            RemoteJointTrajectoryClientReceiver::new(DummyJointTrajectoryClient::new(vec![
                "a".to_owned()
            ]));
        tokio::spawn(client.serve(addr));
        tokio::time::sleep(Duration::from_secs(1)).await;
    }

    let client = RemoteJointTrajectoryClientSender::connect(endpoint).await?;
    assert_eq!(client.joint_names(), vec!["a".to_owned()]);
    assert_eq!(client.current_joint_positions()?, vec![0.0]);
    client
        .send_joint_positions(vec![1.0], Duration::from_secs_f64(0.1))?
        .await?;
    assert_eq!(client.current_joint_positions()?, vec![1.0]);
    client
        .send_joint_trajectory(vec![TrajectoryPoint::new(
            vec![2.0],
            Duration::from_secs_f64(0.1),
        )])?
        .await?;
    assert_eq!(client.current_joint_positions()?, vec![2.0]);
    client
        .send_joint_trajectory(vec![TrajectoryPoint {
            positions: vec![3.0],
            velocities: Some(vec![3.0]),
            time_from_start: Duration::from_secs_f64(0.1),
        }])?
        .await?;
    assert_eq!(client.current_joint_positions()?, vec![3.0]);

    // no wait
    drop(client.send_joint_positions(vec![4.0], Duration::from_secs_f64(0.1))?);
    tokio::time::sleep(Duration::from_secs_f64(0.5)).await;
    assert_eq!(client.current_joint_positions()?, vec![4.0]);
    drop(client.send_joint_trajectory(vec![TrajectoryPoint::new(
        vec![5.0],
        Duration::from_secs_f64(0.1),
    )])?);
    tokio::time::sleep(Duration::from_secs_f64(0.5)).await;
    assert_eq!(client.current_joint_positions()?, vec![5.0]);

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn speaker() -> Result<()> {
    let (addr, endpoint) = endpoint();

    let recv_speaker = Arc::new(DummySpeaker::new());
    // Launch server
    {
        let speaker = RemoteSpeakerReceiver::new(recv_speaker.clone());
        tokio::spawn(speaker.serve(addr));
        tokio::time::sleep(Duration::from_secs(1)).await;
    }

    let speaker = RemoteSpeakerSender::connect(endpoint).await?;
    assert_eq!(recv_speaker.current_message(), "");
    speaker.speak("abc")?.await?;
    assert_eq!(recv_speaker.current_message(), "abc");

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn move_base() -> Result<()> {
    let (addr, endpoint) = endpoint();

    // Launch server
    {
        let base = RemoteMoveBaseReceiver::new(DummyMoveBase::new());
        tokio::spawn(base.serve(addr));
        tokio::time::sleep(Duration::from_secs(1)).await;
    }

    let base = RemoteMoveBaseSender::connect(endpoint).await?;
    let v = base.current_velocity()?;
    assert_approx_eq!(v.x, 0.0);
    assert_approx_eq!(v.y, 0.0);
    assert_approx_eq!(v.theta, 0.0);
    base.send_velocity(&BaseVelocity {
        x: 1.0,
        y: 2.0,
        theta: 3.0,
    })?;
    let v = base.current_velocity()?;
    assert_approx_eq!(v.x, 1.0);
    assert_approx_eq!(v.y, 2.0);
    assert_approx_eq!(v.theta, 3.0);

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn navigation() -> Result<()> {
    let (addr, endpoint) = endpoint();

    let recv_nav = Arc::new(DummyNavigation::new());
    // Launch server
    {
        let nav = RemoteNavigationReceiver::new(recv_nav.clone());
        tokio::spawn(nav.serve(addr));
        tokio::time::sleep(Duration::from_secs(1)).await;
    }

    let nav = RemoteNavigationSender::connect(endpoint).await?;
    nav.send_goal_pose(
        Isometry2::new(Vector2::new(1.0, 2.0), 3.0),
        "",
        Duration::default(),
    )?
    .await?;
    let pose = recv_nav.current_goal_pose()?;
    assert_approx_eq!(pose.translation.x, 1.0);
    assert_approx_eq!(pose.translation.y, 2.0);
    assert_approx_eq!(pose.rotation.angle(), 3.0);
    nav.cancel()?;
    assert!(recv_nav.is_canceled());

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn localization() -> Result<()> {
    let (addr, endpoint) = endpoint();

    // Launch server
    {
        let loc = RemoteLocalizationReceiver::new(DummyLocalization::new());
        tokio::spawn(loc.serve(addr));
        tokio::time::sleep(Duration::from_secs(1)).await;
    }

    let loc = RemoteLocalizationSender::connect(endpoint).await?;
    let pose = loc.current_pose("")?;
    assert_eq!(pose, pose.inverse()); // only identity mapping satisfies this

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn transform_resolver() -> Result<()> {
    let (addr, endpoint) = endpoint();

    let recv_resolver = Arc::new(DummyTransformResolver::default());
    // Launch server
    {
        let resolver = RemoteTransformResolverReceiver::new(recv_resolver.clone());
        tokio::spawn(resolver.serve(addr));
        tokio::time::sleep(Duration::from_secs(1)).await;
    }

    let resolver = RemoteTransformResolverSender::connect(endpoint).await?;
    let transformation = resolver.resolve_transformation("", "", SystemTime::UNIX_EPOCH)?;
    assert_eq!(transformation, transformation.inverse());

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn gamepad() -> Result<()> {
    let (addr, endpoint) = endpoint();

    let recv_gamepad = Arc::new(DummyGamepad::with_all_events());
    // Launch server
    {
        let gamepad = RemoteGamepadReceiver::new(recv_gamepad.clone());
        tokio::spawn(gamepad.serve(addr));
        tokio::time::sleep(Duration::from_secs(1)).await;
    }

    let gamepad = RemoteGamepadSender::connect(endpoint).await?;
    for expected in &recv_gamepad.events {
        match (expected, gamepad.next_event().await) {
            (GamepadEvent::ButtonPressed(expected), GamepadEvent::ButtonPressed(actual)) => {
                assert_eq!(*expected, actual);
            }
            (GamepadEvent::ButtonReleased(expected), GamepadEvent::ButtonReleased(actual)) => {
                assert_eq!(*expected, actual);
            }
            (
                GamepadEvent::AxisChanged(expected_axis, expected_val),
                GamepadEvent::AxisChanged(actual_axis, actual_val),
            ) => {
                assert_eq!(*expected_axis, actual_axis);
                assert_approx_eq!(*expected_val, actual_val);
            }
            (GamepadEvent::Unknown, GamepadEvent::Unknown) => {}
            (expected, actual) => {
                panic!("event mismatch, expected: {expected:?}, actual: {actual:?}")
            }
        }
    }
    gamepad.stop();
    assert!(recv_gamepad.is_stopped());

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn multiple() -> Result<()> {
    let (addr, endpoint) = endpoint();

    let recv_speaker = Arc::new(DummySpeaker::new());
    let recv_nav = Arc::new(DummyNavigation::new());
    let recv_resolver = Arc::new(DummyTransformResolver::default());
    let recv_gamepad = Arc::new(DummyGamepad::with_all_events());
    // Launch server
    {
        let client =
            RemoteJointTrajectoryClientReceiver::new(DummyJointTrajectoryClient::new(vec![
                "a".to_owned()
            ]));
        let speaker = RemoteSpeakerReceiver::new(recv_speaker.clone());
        let base = RemoteMoveBaseReceiver::new(DummyMoveBase::new());
        let nav = RemoteNavigationReceiver::new(recv_nav.clone());
        let loc = RemoteLocalizationReceiver::new(DummyLocalization::new());
        let resolver = RemoteTransformResolverReceiver::new(recv_resolver.clone());
        let gamepad = RemoteGamepadReceiver::new(recv_gamepad.clone());
        tokio::spawn(
            tonic::transport::Server::builder()
                .add_service(client.into_service())
                .add_service(speaker.into_service())
                .add_service(base.into_service())
                .add_service(nav.into_service())
                .add_service(loc.into_service())
                .add_service(resolver.into_service())
                .add_service(gamepad.into_service())
                .serve(addr),
        );
        tokio::time::sleep(Duration::from_secs(1)).await;
    }

    let client = RemoteJointTrajectoryClientSender::connect(endpoint.clone()).await?;
    let speaker = RemoteSpeakerSender::connect(endpoint.clone()).await?;
    let base = RemoteMoveBaseSender::connect(endpoint.clone()).await?;
    let nav = RemoteNavigationSender::connect(endpoint.clone()).await?;
    let loc = RemoteLocalizationSender::connect(endpoint.clone()).await?;
    let resolver = RemoteTransformResolverSender::connect(endpoint.clone()).await?;
    let gamepad = RemoteGamepadSender::connect(endpoint).await?;

    assert_eq!(client.joint_names(), vec!["a".to_owned()]);
    assert_approx_eq!(client.current_joint_positions()?[0], 0.0);
    client
        .send_joint_positions(vec![1.0], Duration::from_secs_f64(0.1))?
        .await?;
    assert_approx_eq!(client.current_joint_positions()?[0], 1.0);
    client
        .send_joint_trajectory(vec![TrajectoryPoint::new(
            vec![2.0],
            Duration::from_secs_f64(0.1),
        )])?
        .await?;
    assert_approx_eq!(client.current_joint_positions()?[0], 2.0);

    let v = base.current_velocity()?;
    assert_approx_eq!(v.x, 0.0);
    assert_approx_eq!(v.y, 0.0);
    assert_approx_eq!(v.theta, 0.0);
    base.send_velocity(&BaseVelocity {
        x: 1.0,
        y: 2.0,
        theta: 3.0,
    })?;
    let v = base.current_velocity()?;
    assert_approx_eq!(v.x, 1.0);
    assert_approx_eq!(v.y, 2.0);
    assert_approx_eq!(v.theta, 3.0);

    speaker.speak("abc")?.await?;
    assert_eq!(recv_speaker.current_message(), "abc");

    nav.send_goal_pose(
        Isometry2::new(Vector2::new(1.0, 2.0), 3.0),
        "",
        Duration::default(),
    )?
    .await?;
    let pose = recv_nav.current_goal_pose()?;
    assert_approx_eq!(pose.translation.x, 1.0);
    assert_approx_eq!(pose.translation.y, 2.0);
    assert_approx_eq!(pose.rotation.angle(), 3.0);

    let pose = loc.current_pose("")?;
    assert_eq!(pose, pose.inverse()); // only identity mapping satisfies this
    nav.cancel()?;
    assert!(recv_nav.is_canceled());

    let transformation = resolver.resolve_transformation("", "", SystemTime::UNIX_EPOCH)?;
    assert_eq!(transformation, transformation.inverse());

    for expected in &recv_gamepad.events {
        match (expected, gamepad.next_event().await) {
            (GamepadEvent::ButtonPressed(expected), GamepadEvent::ButtonPressed(actual)) => {
                assert_eq!(*expected, actual);
            }
            (GamepadEvent::ButtonReleased(expected), GamepadEvent::ButtonReleased(actual)) => {
                assert_eq!(*expected, actual);
            }
            (
                GamepadEvent::AxisChanged(expected_axis, expected_val),
                GamepadEvent::AxisChanged(actual_axis, actual_val),
            ) => {
                assert_eq!(*expected_axis, actual_axis);
                assert_approx_eq!(*expected_val, actual_val);
            }
            (GamepadEvent::Unknown, GamepadEvent::Unknown) => {}
            (expected, actual) => {
                panic!("event mismatch, expected: {expected:?}, actual: {actual:?}")
            }
        }
    }
    gamepad.stop();
    assert!(recv_gamepad.is_stopped());

    Ok(())
}