Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Egui based UI + fix for hang when reading serial number of Nothing Ear (2024) #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ console_error_panic_hook = "0.1.7"
log = "0.4.22"

[workspace]
members = ["nothing", "src-tauri"]
members = ["nothing", "src-egui", "src-tauri"]

[profile.release]
lto = true
Expand Down
5 changes: 3 additions & 2 deletions nothing/src/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ async fn find_address(
.find(|&addr| match addr.0 {
[a, b, c, _, _, _] => a == address[0] && b == address[1] && c == address[2],
})
.ok_or_else(|| {
.ok_or(
"Couldn't find any Ear devices connected. Make sure you're paired with your Ear."
})?;
)?;

Ok(*ear_address)
}
Expand All @@ -39,5 +39,6 @@ pub async fn connect(address: [u8; 3], channel: u8) -> Result<Stream, Box<dyn st
channel,
})
.await?;

Ok(stream)
}
32 changes: 29 additions & 3 deletions nothing/src/nothing_ear_2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::Nothing;
use bluer::rfcomm::Stream;
use bluer::Error;
use std::future::Future;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::Mutex;
use tokio::time::sleep;
Expand Down Expand Up @@ -76,16 +77,41 @@ impl Ear2 {
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 version_str_len: usize = buf[5].into();
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]);
let mut read_total = 0;
let chunk_size = 1;
for chunk in buf.chunks_exact_mut(chunk_size) {
tokio::select! {
result = stream.read(chunk) => {
if let Err(err) = result {
eprintln!("Error reading serial response: {}", err);
break;
}
read_total += chunk_size;
}
() = tokio::time::sleep(Duration::from_secs(1)) => {
eprintln!("Timeout when reading serial response.");
eprintln!("Only got {} bytes: {:?}", read_total, &buf[..read_total]);
break;
}
};
}
let serial_length = 16;

const EAR_2_SERIAL_OFFSET: usize = 37;
const EAR_2024_SERIAL_OFFSET: usize = 31;

let mut serial = String::from_utf8_lossy(&buf[EAR_2_SERIAL_OFFSET..EAR_2_SERIAL_OFFSET+serial_length]);
if serial.contains(',') {
serial = String::from_utf8_lossy(&buf[EAR_2024_SERIAL_OFFSET..EAR_2024_SERIAL_OFFSET+serial_length]);
}

Ok(Self {
address: stream.peer_addr()?.addr.to_string(),
Expand Down
9 changes: 9 additions & 0 deletions src-egui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "nothing-linux-egui"
version = "0.1.0"
edition = "2021"

[dependencies]
eframe = "0.29.1"
nothing = { path = "../nothing" }
tokio = { version = "1.42.0", features = ["full"] }
65 changes: 65 additions & 0 deletions src-egui/src/async_worker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use eframe::egui;
use nothing::{anc::AncMode, nothing_ear_2::Ear2};
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};

pub enum EarCmd {
AncMode(AncMode),
LowLatency(bool),
InEarDetection(bool),
}

pub enum EarResponse {
DeviceInfo(DeviceInfo),
Error(String),
}

pub struct DeviceInfo {
pub address: String,
pub firmware_version: String,
pub serial_number: String,
}

pub async fn async_worker(
mut rx: UnboundedReceiver<EarCmd>,
tx: UnboundedSender<EarResponse>,
ctx: egui::Context,
) {
let send_response = |response: EarResponse| async {
tx.send(response).expect("sending EarResponse");
ctx.request_repaint();
};
let ear_2 = match Ear2::new().await {
Ok(ear_2) => {
send_response(EarResponse::DeviceInfo(DeviceInfo {
address: ear_2.address.clone(),
firmware_version: ear_2.firmware_version.clone(),
serial_number: ear_2.serial_number.clone(),
}))
.await;
ear_2
}
Err(err) => {
send_response(EarResponse::Error(err.to_string())).await;
return;
}
};
while let Some(cmd) = rx.recv().await {
match cmd {
EarCmd::AncMode(anc_mode) => {
if let Err(err) = ear_2.set_anc(anc_mode).await {
send_response(EarResponse::Error(err.to_string())).await;
}
}
EarCmd::LowLatency(mode) => {
if let Err(err) = ear_2.set_low_latency(mode).await {
send_response(EarResponse::Error(err.to_string())).await;
}
}
EarCmd::InEarDetection(mode) => {
if let Err(err) = ear_2.set_in_ear_detection(mode).await {
send_response(EarResponse::Error(err.to_string())).await;
}
}
}
}
}
Loading