haply 0.8.3

Haply Robotics Client Library for the Inverse Service
Documentation
//! Example demonstrating basic haptic interaction with a Haply device.
//! Shows how to connect to a device, read its state, and apply forces based on
//! interaction with virtual objects.

use haply::{
    compute_total_force,
    device_model::{DeviceType, *},
    Cube, HaplyDevice, Sphere,
};
use log::debug;
use tokio;

/// Main entry point demonstrating haptic interaction.
/// Sets up a connection to a Haply device and runs a simple interaction loop
/// with a virtual sphere object.
#[tokio::main]
async fn main() {
    // Set up connection endpoints
    let http_base = "http://localhost:10000"; // HTTP endpoint per docs.
    let ws_url = "ws://localhost:10001"; // WebSocket endpoint per docs.

    let mut device = HaplyDevice::new(http_base, ws_url).await.unwrap();

    // Device setup and initialization
    // Retrieve the list of device configs.
    let configs = device.list_devices().await.unwrap_or_else(|e| {
        debug!("Error listing device configs: {}", e);
        vec![]
    });
    // Map configs to DeviceInfo
    let mut device_infos: Vec<DeviceInfo> = Vec::new();
    for cfg in configs {
        match cfg {
            Config::DeviceConfig(dc) => device_infos.push(dc.device_info.clone()),
            Config::VGConfig(vgc) => device_infos.push(DeviceInfo {
                id: vgc.id.clone(),
                major_version: 0,
                minor_version: 0,
                device_type: DeviceType::VerseGrip,
                uuid: String::new(),
            }),
            Config::WVGConfig(wvgc) => device_infos.push(DeviceInfo {
                id: wvgc.id.clone(),
                major_version: wvgc.major_version,
                minor_version: wvgc.minor_version,
                device_type: DeviceType::WirelessVerseGrip,
                uuid: String::new(),
            }),
        }
    }
    if device_infos.is_empty() {
        debug!("No devices found. Exiting.");
        return;
    }
    let devices_info = DevicesInfo {
        devices: device_infos.clone(),
    };
    debug!("Using device infos: {:?}", devices_info);

    // Save first device info
    let initial_info = devices_info.devices[0].clone();

    // Define physical objects for interaction
    let sphere = Sphere {
        center: [0.05, -0.15, 0.16],
        radius: 0.1,
        stiffness: 1000.0, // Strong restoring force
        magnetism: 0.0,    // No magnetic attraction
        damping: 0.1,      // Light damping
    };
    let cube = Cube {
        center: [3.0, 0.0, 0.0],
        size: 4.0,
        stiffness: 0.8,
        magnetism: 1.5,
        damping: 0.1,
    };

    // Read device state
    let current_state = device.read_state().await.unwrap();
    debug!("Current device state: {:?}", current_state);

    // Send a force update
    let force = Force {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    let id = initial_info.id.clone();
    let force_in = ForceInput {
        device_id: id.clone(),
        forces: force,
    };
    let force_in_vec = vec![force_in];
    if let Err(e) = device
        .update_force(force_in_vec, Some(false), Some(true))
        .await
    {
        debug!("Error updating force: {}", e);
    }

    tokio::time::sleep(std::time::Duration::from_secs(1)).await;

    // Read device state
    let current_state = device.read_state().await.unwrap();
    debug!("Current device state: {:?}", current_state);

    // Collect the objects as trait objects.
    let objects: Vec<Box<dyn haply::physics_model::PhysicalObject>> =
        vec![Box::new(sphere), Box::new(cube)];

    // Main interaction loop
    loop {
        let current_state = device.read_state().await.unwrap();
        // Use the cursor_position if available; otherwise default to [0.0, 0.0, 0.0].
        let position = current_state.inverse3[0]
            .state
            .cursor_position
            .clone()
            .unwrap_or_default();
        debug!("Current cursor position: {:?}", position);
        let orientation = current_state.wireless_verse_grip[0]
            .state
            .orientation
            .clone()
            .unwrap_or_default();
        debug!("Current orientation: {:?}", orientation);

        // Compute and apply forces based on device position
        // Compute the total force from all physical objects.
        let total_force = compute_total_force(position.into(), &objects);
        debug!("Computed total force: {:?}", total_force);

        // Create a Force struct and send it as an update.
        let force = Force {
            x: total_force[0],
            y: total_force[1],
            z: total_force[2],
        };
        let id = current_state.inverse3[0].device_id.clone();
        let force_in = ForceInput {
            device_id: id.clone(),
            forces: force,
        };
        let force_in_vec = vec![force_in];
        if let Err(e) = device
            .update_force(force_in_vec, Some(true), Some(true))
            .await
        {
            debug!("Error updating force: {}", e);
        }

        tokio::time::sleep(std::time::Duration::from_millis(1)).await;
    }
}