use anyhow::Result;
use log::{error, info, warn};
use std::io::{self, BufRead};
use mendi::mendi_client::{MendiClient, MendiClientConfig};
use mendi::types::MendiEvent;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let config = MendiClientConfig {
scan_timeout_secs: 15,
name_prefix: "Mendi".into(),
filter_by_service_uuid: true,
};
let (line_tx, line_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
std::thread::spawn(move || {
let stdin = io::stdin();
for line in stdin.lock().lines() {
match line {
Ok(l) => {
if line_tx.send(l.trim().to_owned()).is_err() {
break;
}
}
Err(_) => break,
}
}
});
let line_rx = std::sync::Arc::new(tokio::sync::Mutex::new(line_rx));
loop {
let client = MendiClient::new(config.clone());
info!("Connecting to Mendi headband …");
let (mut rx, handle) = match client.connect().await {
Ok(r) => r,
Err(e) => {
warn!("Connection failed: {e}");
info!("Retrying in 3 seconds …");
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
continue;
}
};
let handle = std::sync::Arc::new(handle);
info!("Streaming started. Press Ctrl-C or type 'q' + Enter to quit.\n");
info!("Commands:");
info!(" q – quit");
info!(" c – write default calibration (auto-cal on)");
info!(" e – enable sensor");
info!(" d – disable sensor");
let handle_cmd = std::sync::Arc::clone(&handle);
let line_rx_clone = std::sync::Arc::clone(&line_rx);
let cmd_task = tokio::spawn(async move {
let mut rx = line_rx_clone.lock().await;
while let Some(line) = rx.recv().await {
if line.is_empty() {
continue;
}
match line.as_str() {
"q" => {
info!("Quit requested.");
handle_cmd.disconnect().await.ok();
std::process::exit(0);
}
"c" => {
info!("Writing default calibration (auto-cal on) …");
if let Err(e) = handle_cmd
.write_calibration(0.0, 0.0, 0.0, true, false)
.await
{
error!("Calibration write error: {e}");
}
}
"e" => {
info!("Enabling sensor …");
if let Err(e) = handle_cmd.enable_sensor().await {
error!("Enable sensor error: {e}");
}
}
"d" => {
info!("Disabling sensor …");
if let Err(e) = handle_cmd.disable_sensor().await {
error!("Disable sensor error: {e}");
}
}
other => {
info!("Unknown command: '{other}'");
}
}
}
});
let mut frame_count: u64 = 0;
while let Some(event) = rx.recv().await {
match event {
MendiEvent::Connected(info) => {
println!("✅ Connected: {info}");
}
MendiEvent::Disconnected => {
println!("❌ Disconnected.");
break;
}
MendiEvent::Frame(f) => {
frame_count += 1;
if frame_count <= 5 || frame_count.is_multiple_of(10) {
println!(
"[FRAME #{frame_count}] acc=({:+6},{:+6},{:+6}) ang=({:+6},{:+6},{:+6}) \
temp={:.1}°C \
L(ir={:6} red={:6} amb={:6}) \
R(ir={:6} red={:6} amb={:6}) \
P(ir={:6} red={:6} amb={:6})",
f.acc_x, f.acc_y, f.acc_z,
f.ang_x, f.ang_y, f.ang_z,
f.temperature,
f.ir_left, f.red_left, f.amb_left,
f.ir_right, f.red_right, f.amb_right,
f.ir_pulse, f.red_pulse, f.amb_pulse,
);
}
}
MendiEvent::Battery(b) => {
println!(
"[BATTERY] {:.2}V {}% charging={} usb={}",
b.voltage(),
b.percentage(),
b.charging,
b.usb_connected,
);
}
MendiEvent::Calibration(c) => {
println!(
"[CALIBRATION] offsets: L={:.1} R={:.1} P={:.1} \
auto_cal={} low_power={}",
c.offset_left, c.offset_right, c.offset_pulse,
c.auto_calibration, c.low_power_mode,
);
}
MendiEvent::Diagnostics(d) => {
println!(
"[DIAGNOSTICS] imu_ok={} sensor_ok={} adc={:?}",
d.imu_ok,
d.sensor_ok,
d.adc.as_ref().map(|a| format!("{:.2}V ({}%)", a.voltage(), a.percentage())),
);
}
MendiEvent::SensorRead(s) => {
println!(
"[SENSOR] addr=0x{:02X} data=0x{:06X}",
s.address, s.data,
);
}
}
}
cmd_task.abort();
if frame_count > 0 {
info!("Received {frame_count} frames total this session.");
}
info!("Reconnecting in 3 seconds …");
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
}
}