use haply::{
device_model::{ Config, DeviceType, Force, ForceInput },
http::InverseHttpClient,
HaplyDevice,
};
use std::io::Write;
use std::{ env, time::Duration };
use tokio::time::{ interval, sleep };
const USE_HTTP: bool = false;
const HTTP_BASE: &str = "http://localhost:10001";
const WS_URL: &str = "ws://localhost:10001/";
fn has_flag(args: &[String], flag: &str) -> bool {
args.iter().any(|a| a == flag)
}
fn value_after_flag(args: &[String], flag: &str) -> Option<String> {
args.iter()
.position(|a| a == flag)
.and_then(|idx| args.get(idx + 1).cloned())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let args: Vec<String> = env::args().skip(1).collect();
let http_only = has_flag(&args, "--http-only") || USE_HTTP;
let stream_mode = has_flag(&args, "--stream");
let zero_force = has_flag(&args, "--zero-force");
let probe_id = value_after_flag(&args, "--probe");
if stream_mode && zero_force {
eprintln!("--stream and --zero-force cannot be combined; pick one.");
std::process::exit(1);
}
if http_only {
run_http_only(HTTP_BASE).await?;
return Ok(());
}
let mut device = HaplyDevice::new(HTTP_BASE, WS_URL).await?;
if stream_mode {
run_stream_mode(&mut device, probe_id).await;
return Ok(());
}
if zero_force {
run_zero_force_mode(&mut device, probe_id).await;
return Ok(());
}
sleep(Duration::from_millis(500)).await;
device.send_force_full_render().await?;
sleep(Duration::from_millis(300)).await;
let state = device.read_state().await?;
let total =
state.inverse3.len() +
state.verse_grip.len() +
state.wireless_verse_grip.len() +
state.custom_verse_grip.len();
println!("Session ID: {}", state.session_id);
println!("Found {} connected device(s)", total);
let mut idx = 1;
for d in &state.inverse3 {
println!(" #{}: Inverse3 - ID: {}", idx, d.device_id);
idx += 1;
}
for d in &state.verse_grip {
println!(" #{}: VerseGrip - ID: {}", idx, d.device_id);
idx += 1;
}
for d in &state.wireless_verse_grip {
println!(" #{}: WirelessVerseGrip - ID: {}", idx, d.device_id);
idx += 1;
}
for d in &state.custom_verse_grip {
println!(" #{}: CustomVerseGrip - ID: {}", idx, d.device_id);
idx += 1;
}
if total == 0 {
println!("No devices found");
}
Ok(())
}
async fn run_http_only(http_base: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = InverseHttpClient::new(http_base);
let version = client.get_version().await?;
println!("Haply Inverse Service Version: {:?}", version);
let configs = client.get_devices().await?;
println!("Found {} connected device(s)", configs.len());
for (idx, config) in configs.iter().enumerate() {
match config {
Config::DeviceConfig(dc) => {
println!(" #{}: Inverse3 - ID: {}", idx + 1, dc.device_info.id);
}
Config::VGConfig(vg) => {
println!(" #{}: VerseGrip - ID: {}", idx + 1, vg.id);
}
Config::WVGConfig(wvg) => {
let label = match wvg.type_ {
DeviceType::CustomVerseGrip => "CustomVerseGrip",
_ => "WirelessVerseGrip",
};
println!(" #{}: {} - ID: {}", idx + 1, label, wvg.id);
}
}
}
Ok(())
}
async fn run_stream_mode(device: &mut HaplyDevice, probe_id: Option<String>) {
let mut ticker = interval(Duration::from_millis(100));
loop {
ticker.tick().await;
if let Some(id) = &probe_id {
if let Err(err) = device.probe_cursor_position(vec![id.clone()], Some(true)).await {
eprintln!("\rProbe command failed for {}: {}", id, err);
} else {
sleep(Duration::from_millis(200)).await;
}
}
let state = match device.force_read_full_state(None).await {
Ok(state) => state,
Err(err) => {
eprintln!("\rFailed to read full state: {}", err);
continue;
}
};
let inv_count = state.inverse3.len();
let vg_count = state.verse_grip.len();
let wvg_count = state.wireless_verse_grip.len();
let cvg_count = state.custom_verse_grip.len();
print!(
"\rDevices: {} Inverse3, {} VG, {} WVG, {} CVG ",
inv_count,
vg_count,
wvg_count,
cvg_count
);
std::io::stdout().flush().unwrap();
}
}
async fn run_zero_force_mode(device: &mut HaplyDevice, probe_id: Option<String>) {
let mut ticker = interval(Duration::from_millis(100));
loop {
ticker.tick().await;
if let Some(id) = &probe_id {
if let Err(err) = device.probe_cursor_position(vec![id.clone()], Some(true)).await {
eprintln!("\rProbe command failed for {}: {}", id, err);
} else {
sleep(Duration::from_millis(200)).await;
}
}
let state = match device.read_state().await {
Ok(state) => state,
Err(err) => {
eprintln!("\rFailed to read state: {}", err);
continue;
}
};
if let Some(inv) = state.inverse3.first() {
let pos = inv.state.cursor_position.unwrap_or_default();
print!("\r[{}] Pos: ({:.4}, {:.4}, {:.4}) ", inv.device_id, pos.x, pos.y, pos.z);
std::io::stdout().flush().unwrap();
let force_in = ForceInput {
device_id: inv.device_id.clone(),
forces: Force { x: 0.0, y: 0.0, z: 0.0 },
};
if let Err(err) = device.update_force(vec![force_in], Some(true), Some(true)).await {
eprintln!("\rFailed to update force: {}", err);
}
} else {
print!("\rNo Inverse3 devices available ");
std::io::stdout().flush().unwrap();
}
}
}