use crate::device_model::{
AngularPositionInput,
AngularTorqueInput,
Command,
Config,
ExtensionDataInput,
ForceInput,
Inverse3CommandClear,
Inverse3Configure,
PositionInput,
SdfHfxObject,
ServiceData,
ServiceMsg,
SessionConfigure,
TimestampedServiceData,
VerseGripCommandClear,
VerseGripConfigure,
VerseGripDuplicateMode,
VersionResponse,
};
use crate::state::{
clear_inverse3_commands_in_msg,
clear_oneshot_fields,
clear_verse_grip_commands_in_msg,
update_entire_msg,
update_state_on_message,
VerseGripLane,
};
use crate::http::InverseHttpClient;
use crate::websocket::InverseWebSocketClient;
use log::{ error, trace };
use std::sync::Arc;
use tokio::sync::Mutex;
#[cfg(test)]
#[path = "unit_tests.rs"]
mod unit_tests;
pub struct HaplyDevice {
http_client: InverseHttpClient,
pub ws_client: InverseWebSocketClient,
pub state: Arc<Mutex<TimestampedServiceData>>,
pub device_cmd_msg: Arc<Mutex<ServiceMsg>>,
verse_grip_mode: VerseGripDuplicateMode,
}
impl HaplyDevice {
pub async fn new(
http_base: &str,
ws_url: &str
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
Self::with_options(http_base, ws_url, VerseGripDuplicateMode::default()).await
}
pub async fn with_options(
http_base: &str,
ws_url: &str,
verse_grip_mode: VerseGripDuplicateMode
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let http_client = InverseHttpClient::with_options(http_base, verse_grip_mode);
let mut ws_client = InverseWebSocketClient::new(ws_url);
ws_client.connect().await?;
let initial_state = TimestampedServiceData {
data: ServiceData::default(),
timestamp: std::time::Instant::now(),
};
let device = Self {
http_client,
ws_client: ws_client.clone(),
state: Arc::new(Mutex::new(initial_state)),
device_cmd_msg: Arc::new(Mutex::new(ServiceMsg::default())),
verse_grip_mode,
};
device.start_listening();
Ok(device)
}
pub async fn shutdown(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.ws_client.disconnect().await
}
fn start_listening(&self) {
#[cfg(feature = "tracy")]
let _span = tracy_client::span!("device::start_listening");
let state_clone = Arc::clone(&self.state);
let mut ws_client = self.ws_client.clone();
let mode = self.verse_grip_mode;
tokio::spawn(async move {
let mut first_msg = true;
ws_client.listen(move |message| {
let state_clone = Arc::clone(&state_clone);
async move {
update_state_on_message(state_clone, message, &mut first_msg, mode).await;
}
}).await;
});
}
pub async fn read_state(
&self
) -> Result<ServiceData, Box<dyn std::error::Error + Send + Sync>> {
#[cfg(feature = "tracy")]
let _span = tracy_client::span!("device::read_state");
trace!("Reading device state");
let data = {
#[cfg(feature = "tracy")]
let _lock_span = tracy_client::span!("device::read_state_lock_and_clone");
self.state.lock().await.data.clone()
};
Ok(data)
}
pub async fn force_read_full_state(
&mut self,
timeout: Option<std::time::Duration>
) -> Result<ServiceData, Box<dyn std::error::Error + Send + Sync>> {
let start = std::time::Instant::now();
self.send_force_full_render().await?;
if let Some(dur) = timeout {
loop {
let elapsed = start.elapsed();
if elapsed >= dur {
error!("Timeout waiting for full state update");
return Err("Timeout waiting for full state update".into());
}
let last_update = self.state.lock().await;
if last_update.timestamp >= start {
trace!("State updated after force render");
break;
}
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
}
self.read_state().await
}
pub async fn update_force(
&mut self,
forces_input: Vec<ForceInput>,
execute_check: Option<bool>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let commands: Vec<Command> = forces_input
.into_iter()
.map(|fi| Command::SetCursorForce {
device_id: fi.device_id,
vector: fi.forces,
execute: execute_check.unwrap_or(true),
})
.collect();
self.update_command_msg(commands).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn update_position(
&mut self,
positions_input: Vec<PositionInput>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let commands: Vec<Command> = positions_input
.into_iter()
.map(|pi| Command::SetCursorPosition {
device_id: pi.device_id,
position: pi.positions,
execute: true,
})
.collect();
self.update_command_msg(commands).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn update_angular_torques(
&mut self,
inputs: Vec<AngularTorqueInput>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let commands: Vec<Command> = inputs
.into_iter()
.map(|i| Command::SetAngularTorques {
device_id: i.device_id,
torques: i.torques,
execute: true,
})
.collect();
self.update_command_msg(commands).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn update_angular_position(
&mut self,
inputs: Vec<AngularPositionInput>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let commands: Vec<Command> = inputs
.into_iter()
.map(|i| Command::SetAngularPosition {
device_id: i.device_id,
angles: i.angles,
execute: true,
})
.collect();
self.update_command_msg(commands).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn update_extension_data(
&mut self,
extension_data_inputs: Vec<ExtensionDataInput>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let commands: Vec<Command> = extension_data_inputs
.into_iter()
.map(|edi| Command::SetExtensionData {
device_id: edi.device_id,
extension_data: edi.extension_data,
})
.collect();
self.update_command_msg(commands).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn probe_cursor_position(
&mut self,
device_ids: Vec<String>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let commands: Vec<Command> = device_ids
.into_iter()
.map(|id| Command::ProbePosition { device_id: id })
.collect();
self.update_command_msg(commands).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
#[deprecated(
note = "Use probe_cursor_position instead — 3.5 unified probe_position covers both"
)]
pub async fn probe_angular_position(
&mut self,
device_ids: Vec<String>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.probe_cursor_position(device_ids, send).await
}
pub async fn probe_orientation(
&mut self,
device_ids: Vec<String>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let commands: Vec<Command> = device_ids
.into_iter()
.map(|id| Command::ProbeOrientation { device_id: id })
.collect();
self.update_command_msg(commands).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn configure_inverse3(
&mut self,
device_id: &str,
config: Inverse3Configure,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let command = Command::ConfigureInverse3 {
device_id: device_id.to_string(),
config,
};
self.update_command_msg(vec![command]).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn configure_versegrip(
&mut self,
device_id: &str,
config: VerseGripConfigure,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let command = Command::ConfigureVerseGrip {
device_id: device_id.to_string(),
config,
};
self.update_command_msg(vec![command]).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn configure_session(
&mut self,
config: SessionConfigure,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let command = Command::ConfigureSession(config);
self.update_command_msg(vec![command]).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn sdf_set(
&mut self,
device_id: &str,
objects: Vec<SdfHfxObject>,
from_space: Option<String>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let command = Command::SdfSet {
device_id: device_id.to_string(),
objects,
from_space,
};
self.update_command_msg(vec![command]).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn sdf_update(
&mut self,
device_id: &str,
objects: Vec<SdfHfxObject>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let command = Command::SdfUpdate {
device_id: device_id.to_string(),
objects,
};
self.update_command_msg(vec![command]).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn sdf_remove(
&mut self,
device_id: &str,
ids: Vec<String>,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let command = Command::SdfRemove {
device_id: device_id.to_string(),
ids,
};
self.update_command_msg(vec![command]).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn configure_sdf_output(
&mut self,
device_id: &str,
state_output: bool,
send: Option<bool>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let command = Command::ConfigureSdfOutput {
device_id: device_id.to_string(),
state_output,
};
self.update_command_msg(vec![command]).await?;
if send.unwrap_or(false) {
self.send_command().await?;
}
Ok(())
}
pub async fn send_force_full_render(
&mut self
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.update_command_msg(vec![Command::ForceRenderFullState]).await?;
self.send_command().await?;
Ok(())
}
pub async fn send_ping(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.ws_client.send_message("{}").await?;
Ok(())
}
pub async fn update_command_msg(
&mut self,
commands: Vec<Command>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
#[cfg(feature = "tracy")]
let _span = tracy_client::span!("device::update_command_msg");
let mut service_msg = {
#[cfg(feature = "tracy")]
let _lock_span = tracy_client::span!("device::update_command_msg_lock");
self.device_cmd_msg.lock().await
};
{
#[cfg(feature = "tracy")]
let _merge_span = tracy_client::span!("device::update_command_msg_merge");
update_entire_msg(&commands, &mut service_msg)?;
}
Ok(())
}
pub async fn send_command(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
#[cfg(feature = "tracy")]
let _span = tracy_client::span!("device::send_command");
let mut msg = {
#[cfg(feature = "tracy")]
let _lock_span = tracy_client::span!("device::send_command_lock");
self.device_cmd_msg.lock().await
};
let cmd_text = {
#[cfg(feature = "tracy")]
let _serialize_span = tracy_client::span!("device::send_command_serialize");
serde_json::to_string(&*msg)?
};
{
#[cfg(feature = "tracy")]
let _send_span = tracy_client::span!("device::send_command_ws_send");
self.ws_client.send_message(&cmd_text).await?;
}
{
#[cfg(feature = "tracy")]
let _clear_span = tracy_client::span!("device::send_command_clear_oneshot");
clear_oneshot_fields(&mut msg);
}
Ok(())
}
pub async fn clear_inverse3_commands(
&mut self,
device_id: &str,
what: Inverse3CommandClear,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if !what.any() {
return Ok(());
}
#[cfg(feature = "tracy")]
let _span = tracy_client::span!("device::clear_inverse3_commands");
let mut msg = {
#[cfg(feature = "tracy")]
let _lock_span = tracy_client::span!("device::clear_inverse3_commands_lock");
self.device_cmd_msg.lock().await
};
clear_inverse3_commands_in_msg(&mut msg, device_id, what);
Ok(())
}
pub async fn clear_verse_grip_commands(
&mut self,
device_id: &str,
what: VerseGripCommandClear,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if !what.any() {
return Ok(());
}
#[cfg(feature = "tracy")]
let _span = tracy_client::span!("device::clear_verse_grip_commands");
let mut msg = self.device_cmd_msg.lock().await;
clear_verse_grip_commands_in_msg(&mut msg, VerseGripLane::Wired, device_id, what);
Ok(())
}
pub async fn clear_wireless_verse_grip_commands(
&mut self,
device_id: &str,
what: VerseGripCommandClear,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if !what.any() {
return Ok(());
}
#[cfg(feature = "tracy")]
let _span = tracy_client::span!("device::clear_wireless_verse_grip_commands");
let mut msg = self.device_cmd_msg.lock().await;
clear_verse_grip_commands_in_msg(&mut msg, VerseGripLane::Wireless, device_id, what);
Ok(())
}
pub async fn list_devices(
&self
) -> Result<Vec<Config>, Box<dyn std::error::Error + Send + Sync>> {
let configs = self.http_client.get_devices().await?;
Ok(configs)
}
pub async fn get_devices(
&self
) -> Result<Vec<Config>, Box<dyn std::error::Error + Send + Sync>> {
let state = self.state.lock().await;
let mut configs = Vec::new();
for d in &state.data.inverse3 {
let config = d.config.clone().ok_or("Missing config for Inverse3 device")?;
configs.push(Config::DeviceConfig(config));
}
for d in &state.data.verse_grip {
let config = d.config.clone().ok_or("Missing config for Verse Grip device")?;
configs.push(Config::VGConfig(config));
}
for d in &state.data.wireless_verse_grip {
let config = d.config.clone().ok_or("Missing config for Wireless Verse Grip device")?;
configs.push(Config::WVGConfig(config));
}
for d in &state.data.custom_verse_grip {
let config = d.config.clone().ok_or("Missing config for Custom Verse Grip device")?;
configs.push(Config::WVGConfig(config));
}
Ok(configs)
}
pub async fn get_device_info(
&self,
device_id: &str
) -> Result<Config, Box<dyn std::error::Error + Send + Sync>> {
let devices = self.list_devices().await?;
let device = devices.into_iter().find(|c| {
match c {
Config::DeviceConfig(dc) => dc.device_info.id == device_id,
Config::VGConfig(vgc) => vgc.id == device_id,
Config::WVGConfig(wvgc) => wvgc.id == device_id,
}
});
match device {
Some(cfg) => Ok(cfg),
None => Err(format!("Device {} not found", device_id).into()),
}
}
pub async fn get_service_version(
&self
) -> Result<VersionResponse, Box<dyn std::error::Error + Send + Sync>> {
let version = self.http_client.get_version().await?;
Ok(version)
}
}