From fe8ec3ecf5112cef89942307bb07b5dff42a42a7 Mon Sep 17 00:00:00 2001 From: sn99 Date: Sun, 6 Apr 2025 21:03:51 +0530 Subject: [PATCH] Add reconnect functionality and improve error handling for Ear2 device --- nothing/src/connect.rs | 22 +++-- nothing/src/lib.rs | 15 ++-- nothing/src/nothing_ear_2.rs | 153 ++++++++++++++++++++++------------- src-tauri/src/lib.rs | 74 +++++++++-------- src/app.rs | 13 +++ styles.css | 20 +++++ 6 files changed, 189 insertions(+), 108 deletions(-) diff --git a/nothing/src/connect.rs b/nothing/src/connect.rs index 9c5651a..f055e31 100644 --- a/nothing/src/connect.rs +++ b/nothing/src/connect.rs @@ -1,18 +1,13 @@ use bluer::rfcomm::Stream; -use bluer::Address; - -async fn set_powered_adapter( - session: bluer::Session, -) -> Result> { +use bluer::Error; +use bluer::{Address, ErrorKind}; +async fn set_powered_adapter(session: bluer::Session) -> Result { let adapter = session.default_adapter().await?; adapter.set_powered(true).await?; Ok(adapter) } -async fn find_address( - adapter: bluer::Adapter, - address: [u8; 3], -) -> Result> { +async fn find_address(adapter: bluer::Adapter, address: [u8; 3]) -> Result { let device_addresses = adapter.device_addresses().await?; let ear_address = device_addresses @@ -20,14 +15,17 @@ async fn find_address( .find(|&addr| match addr.0 { [a, b, c, _, _, _] => a == address[0] && b == address[1] && c == address[2], }) - .ok_or_else(|| { - "Couldn't find any Ear devices connected. Make sure you're paired with your Ear." + .ok_or(Error { + kind: ErrorKind::ConnectionAttemptFailed, + message: + "Couldn't find any Ear devices connected. Make sure you're paired with your Ear." + .to_string(), })?; Ok(*ear_address) } -pub async fn connect(address: [u8; 3], channel: u8) -> Result> { +pub async fn connect(address: [u8; 3], channel: u8) -> Result { let session = bluer::Session::new().await?; let adapter = set_powered_adapter(session).await?; let ear_address = find_address(adapter, address).await?; diff --git a/nothing/src/lib.rs b/nothing/src/lib.rs index ef9ec1d..e494a34 100644 --- a/nothing/src/lib.rs +++ b/nothing/src/lib.rs @@ -5,22 +5,25 @@ pub mod connect; pub mod nothing_ear_2; pub trait Nothing { - fn get_address(&self) -> impl std::future::Future + Send; - fn get_firmware_version(&self) -> impl std::future::Future + Send; - fn get_serial_number(&self) -> impl std::future::Future + Send; + fn get_address(&self) -> impl std::future::Future> + Send; + fn get_firmware_version(&self) -> impl std::future::Future> + Send; + fn get_serial_number(&self) -> impl std::future::Future> + Send; fn set_anc_mode( - &self, + &mut self, mode: AncMode, ) -> impl std::future::Future> + Send; fn set_low_latency_mode( - &self, + &mut self, mode: bool, ) -> impl std::future::Future> + Send; fn set_in_ear_detection_mode( - &self, + &mut self, mode: bool, ) -> impl std::future::Future> + Send; + + fn try_connect(&mut self) + -> impl std::future::Future> + Send; } diff --git a/nothing/src/nothing_ear_2.rs b/nothing/src/nothing_ear_2.rs index 06a0d14..f687477 100644 --- a/nothing/src/nothing_ear_2.rs +++ b/nothing/src/nothing_ear_2.rs @@ -2,7 +2,7 @@ use crate::anc::AncMode; use crate::connect::connect; use crate::Nothing; use bluer::rfcomm::Stream; -use bluer::Error; +use bluer::{Error, ErrorKind}; use std::future::Future; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::Mutex; @@ -34,71 +34,95 @@ const EAR2_IN_EAR_DETECTION_OFF: [u8; 13] = [ 0x55, 0x60, 0x01, 0x04, 0xf0, 0x03, 0x00, 0x25, 0x01, 0x01, 0x00, 0xb2, 0x94, ]; +pub type Ear2State = Mutex; + pub struct Ear2 { - pub address: String, - pub firmware_version: String, - pub serial_number: String, - stream: Mutex, + pub address: Option, + pub firmware_version: Option, + pub serial_number: Option, + stream: Option, } impl Nothing for Ear2 { - async fn get_address(&self) -> String { + async fn get_address(&self) -> Option { self.address.clone() } - async fn get_firmware_version(&self) -> String { + async fn get_firmware_version(&self) -> Option { self.firmware_version.clone() } - async fn get_serial_number(&self) -> String { + async fn get_serial_number(&self) -> Option { self.serial_number.clone() } - fn set_anc_mode(&self, mode: AncMode) -> impl Future> + Send { + fn set_anc_mode(&mut self, mode: AncMode) -> impl Future> + Send { self.set_anc(mode) } - fn set_low_latency_mode(&self, mode: bool) -> impl Future> + Send { + fn set_low_latency_mode( + &mut self, + mode: bool, + ) -> impl Future> + Send { self.set_low_latency(mode) } fn set_in_ear_detection_mode( - &self, + &mut self, mode: bool, ) -> impl Future> + Send { self.set_in_ear_detection(mode) } + + fn try_connect(&mut self) -> impl Future> + Send { + self.try_connect() + } } impl Ear2 { pub async fn new() -> bluer::Result { - let mut stream = Self::fetch_stream().await; - - // get firmware version - stream.write_all(&EAR2_FIRMWARE).await?; - let mut buf = [0_u8; 8]; - stream.read_exact(&mut buf).await?; - let version_str_len: usize = buf[5].try_into().unwrap(); - let mut buf = vec![0_u8; version_str_len + 2]; - stream.read_exact(&mut buf).await?; - let version = String::from_utf8_lossy(&buf[..version_str_len]); - - // get serial number - stream.write_all(&EAR2_SERIAL).await?; - let mut buf = [0_u8; 64 + 64 + 18]; - stream.read_exact(&mut buf).await?; - let serial = String::from_utf8_lossy(&buf[37..53]); - - Ok(Self { - address: stream.peer_addr()?.addr.to_string(), - firmware_version: version.to_string(), - serial_number: serial.to_string(), - stream: Mutex::new(stream), - }) + let mut ear_2 = Self { + address: None, + firmware_version: None, + serial_number: None, + stream: None, + }; + + // try to connect to the device + ear_2.try_connect().await?; + + Ok(ear_2) + } + + pub async fn try_connect(&mut self) -> bluer::Result<()> { + let stream = Self::fetch_stream().await; + if let Some(mut stream) = stream { + // get firmware version + stream.write_all(&EAR2_FIRMWARE).await?; + let mut buf = [0_u8; 8]; + stream.read_exact(&mut buf).await?; + let version_str_len: usize = buf[5].try_into().unwrap(); + let mut buf = vec![0_u8; version_str_len + 2]; + stream.read_exact(&mut buf).await?; + let version = String::from_utf8_lossy(&buf[..version_str_len]); + + // get serial number + stream.write_all(&EAR2_SERIAL).await?; + let mut buf = [0_u8; 64 + 64 + 18]; + stream.read_exact(&mut buf).await?; + let serial = String::from_utf8_lossy(&buf[37..53]); + + self.address = Some(stream.peer_addr()?.addr.to_string()); + self.firmware_version = Some(version.to_string()); + self.serial_number = Some(serial.to_string()); + self.stream = Some(stream); + } + + Ok(()) } - pub async fn fetch_stream() -> Stream { + pub async fn fetch_stream() -> Option { let mut stream = connect(EAR2_ADDRESS, EAR2_CHANNEL).await; for i in 1..=RETRY { - sleep(std::time::Duration::from_millis(i * 500)).await; + sleep(std::time::Duration::from_millis(i * 690)).await; match stream { Ok(s) => { stream = Ok(s); @@ -111,40 +135,53 @@ impl Ear2 { } } - stream.expect("Failed to connect to Ear") + stream.ok() } - pub async fn set_anc(&self, mode: AncMode) -> bluer::Result<()> { + pub async fn set_anc(&mut self, mode: AncMode) -> bluer::Result<()> { let mut buf = EAR2_ANC; buf[9] = mode.into(); - let mut stream = self.stream.lock().await; - stream.write_all(&buf).await?; - - Ok(()) + if let Some(stream) = &mut self.stream { + stream.write_all(&buf).await?; + Ok(()) + } else { + Err(Error { + kind: ErrorKind::ConnectionAttemptFailed, + message: "set_acn failed because of no connection".to_string(), + }) + } } - pub async fn set_low_latency(&self, mode: bool) -> bluer::Result<()> { - let mut stream = self.stream.lock().await; - - if mode { - stream.write_all(&EAR2_LOW_LAG_ON).await?; + pub async fn set_low_latency(&mut self, mode: bool) -> bluer::Result<()> { + if let Some(stream) = &mut self.stream { + if mode { + stream.write_all(&EAR2_LOW_LAG_ON).await?; + } else { + stream.write_all(&EAR2_LOW_LAG_OFF).await?; + } + Ok(()) } else { - stream.write_all(&EAR2_LOW_LAG_OFF).await?; + Err(Error { + kind: ErrorKind::ConnectionAttemptFailed, + message: "set_low_latency failed because of no connection".to_string(), + }) } - - Ok(()) } - pub async fn set_in_ear_detection(&self, mode: bool) -> bluer::Result<()> { - let mut stream = self.stream.lock().await; - - if mode { - stream.write_all(&EAR2_IN_EAR_DETECTION_ON).await?; + pub async fn set_in_ear_detection(&mut self, mode: bool) -> bluer::Result<()> { + if let Some(stream) = &mut self.stream { + if mode { + stream.write_all(&EAR2_IN_EAR_DETECTION_ON).await?; + } else { + stream.write_all(&EAR2_IN_EAR_DETECTION_OFF).await?; + } + Ok(()) } else { - stream.write_all(&EAR2_IN_EAR_DETECTION_OFF).await?; + Err(Error { + kind: ErrorKind::ConnectionAttemptFailed, + message: "set_in_ear_detection failed because of no connection".to_string(), + }) } - - Ok(()) } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e5c7451..0ef8de5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,104 +1,113 @@ use nothing::anc::AncMode; -use nothing::nothing_ear_2::Ear2; +use nothing::nothing_ear_2::{Ear2State, Ear2}; use nothing::Nothing; +use tokio::sync::Mutex; #[tauri::command] -async fn get_firmware_version(state: tauri::State<'_, Ear2>) -> Result { - let firmware_version = state.get_firmware_version().await; - println!("{}", firmware_version); +async fn get_firmware_version(state: tauri::State<'_, Ear2State>) -> Result, ()> { + let firmware_version = state.lock().await.get_firmware_version().await; + println!("firmware_version: {:?}", firmware_version); Ok(firmware_version) } #[tauri::command] -async fn get_address(state: tauri::State<'_, Ear2>) -> Result { - let address = state.get_address().await; - println!("{}", address); +async fn get_address(state: tauri::State<'_, Ear2State>) -> Result, ()> { + let address = state.lock().await.get_address().await; + println!("address: {:?}", address); Ok(address) } #[tauri::command] -async fn get_serial_number(state: tauri::State<'_, Ear2>) -> Result { - let serial_number = state.get_serial_number().await; - println!("{}", serial_number); +async fn get_serial_number(state: tauri::State<'_, Ear2State>) -> Result, ()> { + let serial_number = state.lock().await.get_serial_number().await; + println!("serial_number: {:?}", serial_number); Ok(serial_number) } #[tauri::command] -async fn set_anc_mode_off(state: tauri::State<'_, Ear2>) -> Result<(), ()> { - let k = state.set_anc_mode(AncMode::Off).await; +async fn set_anc_mode_off(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let k = state.lock().await.set_anc_mode(AncMode::Off).await; println!("set_anc_mode_off: {:?}", k); Ok(()) } #[tauri::command] -async fn set_anc_mode_high(state: tauri::State<'_, Ear2>) -> Result<(), ()> { - let k = state.set_anc_mode(AncMode::High).await; +async fn set_anc_mode_high(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let k = state.lock().await.set_anc_mode(AncMode::High).await; println!("set_anc_mode_high: {:?}", k); Ok(()) } #[tauri::command] -async fn set_anc_mode_mid(state: tauri::State<'_, Ear2>) -> Result<(), ()> { - let k = state.set_anc_mode(AncMode::Mid).await; +async fn set_anc_mode_mid(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let k = state.lock().await.set_anc_mode(AncMode::Mid).await; println!("set_anc_mode_mid: {:?}", k); Ok(()) } #[tauri::command] -async fn set_anc_mode_low(state: tauri::State<'_, Ear2>) -> Result<(), ()> { - let k = state.set_anc_mode(AncMode::Low).await; +async fn set_anc_mode_low(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let k = state.lock().await.set_anc_mode(AncMode::Low).await; println!("set_anc_mode_low: {:?}", k); Ok(()) } #[tauri::command] -async fn set_anc_mode_adaptive(state: tauri::State<'_, Ear2>) -> Result<(), ()> { - let k = state.set_anc_mode(AncMode::Adaptive).await; +async fn set_anc_mode_adaptive(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let k = state.lock().await.set_anc_mode(AncMode::Adaptive).await; println!("set_anc_mode_adaptive: {:?}", k); Ok(()) } #[tauri::command] -async fn set_anc_mode_transparency(state: tauri::State<'_, Ear2>) -> Result<(), ()> { - let k = state.set_anc_mode(AncMode::Transparency).await; +async fn set_anc_mode_transparency(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let k = state.lock().await.set_anc_mode(AncMode::Transparency).await; println!("set_anc_mode_transparency: {:?}", k); Ok(()) } #[tauri::command] -async fn set_low_lag_mode_on(state: tauri::State<'_, Ear2>) -> Result<(), ()> { - let k = state.set_low_latency_mode(true).await; +async fn set_low_lag_mode_on(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let k = state.lock().await.set_low_latency_mode(true).await; println!("set_low_lag_mode_on: {:?}", k); Ok(()) } #[tauri::command] -async fn set_low_lag_mode_off(state: tauri::State<'_, Ear2>) -> Result<(), ()> { - let k = state.set_low_latency_mode(false).await; +async fn set_low_lag_mode_off(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let k = state.lock().await.set_low_latency_mode(false).await; println!("set_low_lag_mode_false: {:?}", k); Ok(()) } #[tauri::command] -async fn set_in_ear_detection_on(state: tauri::State<'_, Ear2>) -> Result<(), ()> { - let k = state.set_in_ear_detection_mode(true).await; +async fn set_in_ear_detection_on(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let k = state.lock().await.set_in_ear_detection_mode(true).await; println!("set_in_ear_detection_on: {:?}", k); Ok(()) } #[tauri::command] -async fn set_in_ear_detection_off(state: tauri::State<'_, Ear2>) -> Result<(), ()> { - let k = state.set_in_ear_detection_mode(false).await; +async fn set_in_ear_detection_off(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let k = state.lock().await.set_in_ear_detection_mode(false).await; println!("set_in_ear_detection_off: {:?}", k); Ok(()) } +#[tauri::command] +async fn try_reconnect(state: tauri::State<'_, Ear2State>) -> Result<(), ()> { + let mut ear2 = state.lock().await; + let k = ear2.try_connect().await; + println!("try_reconnect: {:?}", k); + Ok(()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub async fn run() { let ear = Ear2::new().await.unwrap(); tauri::Builder::default() - .manage(ear) + .manage(Mutex::new(ear)) .plugin(tauri_plugin_shell::init()) .invoke_handler(tauri::generate_handler![ get_firmware_version, @@ -113,7 +122,8 @@ pub async fn run() { set_low_lag_mode_on, set_low_lag_mode_off, set_in_ear_detection_on, - set_in_ear_detection_off + set_in_ear_detection_off, + try_reconnect ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/app.rs b/src/app.rs index db9a614..3c07067 100644 --- a/src/app.rs +++ b/src/app.rs @@ -59,6 +59,13 @@ pub fn App() -> impl IntoView { }); }; + let try_reconnect = move || { + spawn_local(async move { + log!("Try-Reconnect"); + invoke("try_reconnect").await; + }) + }; + view! {
@@ -181,6 +188,12 @@ pub fn App() -> impl IntoView {

{move || info.get()}

+ +
+ + + +
} } diff --git a/styles.css b/styles.css index b4dadc0..62b16c5 100644 --- a/styles.css +++ b/styles.css @@ -136,3 +136,23 @@ input:checked + .slider:before { .hidden { visibility: hidden; } + +.reconnect-icon { + position: absolute; + bottom: 2vh; + right: 2vh; + width: 5vh; + height: 5vh; + cursor: pointer; + transition: transform 0.3s; +} + +.reconnect-icon:hover { + transform: scale(1.1); +} + +.reconnect-icon svg { + width: 100%; + height: 100%; + fill: #dcdcdc; +} \ No newline at end of file