first commit

This commit is contained in:
Lucas Schumacher 2024-04-15 21:38:05 -04:00
commit 710522ba3d
5 changed files with 1541 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1290
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "bleeper"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.82"
btleplug = "0.11.5"
crossterm = "0.27.0"
futures = "0.3.30"
ratatui = "0.26.1"
tokio = { version = "1.37.0", features = ["full"] }

147
src/app.rs Normal file
View File

@ -0,0 +1,147 @@
use anyhow::anyhow;
use crossterm::event::{self, Event, KeyCode};
use ratatui::prelude::*;
use ratatui::widgets::{Block, Borders, List, ListItem, ListState};
use std::io::Stdout;
use std::sync::mpsc::{channel, Receiver, Sender};
pub enum AppEvent {
Term(Event),
Log(String),
}
pub struct App {
events: Receiver<AppEvent>,
logs: Vec<String>,
log_state: ListState,
}
impl App {
pub fn new() -> Self {
let (send, recv) = channel::<AppEvent>();
let ble_sender = send.clone();
std::thread::spawn(move || term_event_sender(send));
let async_runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
async_runtime.spawn(async move {
let e = ble_event_sender(&ble_sender)
.await
.expect_err("Bluetooth thread closed");
ble_sender
.send(AppEvent::Log(format!("Unrecoverable Error: {e}")))
.expect("mpsc fail :(");
});
/*
std::thread::spawn(move || {
let e = ble_event_sender(&ble_sender)
.block_on()
.expect_err("Bluetooth thread closed");
ble_sender
.send(AppEvent::Log(format!("Unrecoverable Error: {}", e)))
.expect("mpsc fail :(");
});
*/
Self {
events: recv,
logs: vec![format!("Hello World!")],
log_state: ListState::default(),
}
}
fn ui(&mut self, f: &mut Frame) {
//let greeting = widgets::Paragraph::new("Hello World!");
//f.render_widget(greeting, f.size());
let logs: Vec<ListItem> = self
.logs
.iter()
.map(|s| ListItem::new(vec![text::Line::from(Span::raw(s))]))
.collect();
let logs = List::new(logs).block(Block::default().borders(Borders::ALL).title("Logs"));
f.render_stateful_widget(logs, f.size(), &mut self.log_state);
}
pub fn run(&mut self, term: &mut Terminal<CrosstermBackend<Stdout>>) -> anyhow::Result<()> {
loop {
term.draw(|f| self.ui(f))?;
match self.events.recv()? {
AppEvent::Term(Event::Key(key)) if key.code == KeyCode::Char('q') => {
return Ok(());
}
AppEvent::Term(Event::Key(key)) if key.code == KeyCode::Char('w') => {
self.logs.push(format!("w was pressed!"));
}
AppEvent::Term(_) => {}
AppEvent::Log(message) => self.logs.push(message),
}
}
}
}
fn term_event_sender(tx: Sender<AppEvent>) {
while let Ok(e) = event::read() {
if tx.send(AppEvent::Term(e)).is_err() {
break;
}
}
}
async fn ble_event_sender(tx: &Sender<AppEvent>) -> anyhow::Result<()> {
use btleplug::api::{Central, CentralEvent, Manager as _, ScanFilter};
use btleplug::platform::Manager;
use futures::stream::StreamExt;
tx.send(AppEvent::Log(format!("Starting Bluetooth thread")))?;
let manager = Manager::new().await?;
tx.send(AppEvent::Log(format!("Got bluetooth manager!")))?;
let mut adapter_list = manager.adapters().await?;
tx.send(AppEvent::Log(format!(
"Found {} bluetooth adapters",
adapter_list.len()
)))?;
for adapter in &adapter_list {
tx.send(AppEvent::Log(format!(
"\t{}",
adapter.adapter_info().await?
)))?;
}
let adapter = adapter_list
.pop()
.ok_or(anyhow!("Error: no adapters found"))?;
let mut events = adapter.events().await?;
adapter.start_scan(ScanFilter::default()).await?;
while let Some(event) = events.next().await {
match event {
CentralEvent::DeviceDiscovered(id) => {
tx.send(AppEvent::Log(format!("Discov: {:?}", id)))?;
}
CentralEvent::DeviceUpdated(id) => {
tx.send(AppEvent::Log(format!("Update: {:?}", id)))?;
}
CentralEvent::DeviceConnected(id) => {
tx.send(AppEvent::Log(format!("Connec: {:?}", id)))?;
}
CentralEvent::DeviceDisconnected(id) => {
tx.send(AppEvent::Log(format!("Discon: {:?}", id)))?;
}
CentralEvent::ManufacturerDataAdvertisement {
id,
manufacturer_data,
} => {
tx.send(AppEvent::Log(format!("ManAdv: {:?}", id)))?;
let _ = manufacturer_data;
}
CentralEvent::ServiceDataAdvertisement { id, service_data } => {
tx.send(AppEvent::Log(format!("DatAdv: {:?}", id)))?;
let _ = service_data;
}
CentralEvent::ServicesAdvertisement { id, services } => {
tx.send(AppEvent::Log(format!("SrvAdv: {:?}", id)))?;
let _ = services;
}
}
}
Err(anyhow!("Error: Bluetooth closed"))
}

89
src/main.rs Normal file
View File

@ -0,0 +1,89 @@
//use anyhow::{bail, Result};
//use btleplug::api::{bleuuid::BleUuid, Central, CentralEvent, Manager as _, ScanFilter};
//use btleplug::platform::{Adapter, Manager};
//use futures::stream::{Stream, StreamExt};
//
//#[tokio::main]
//async fn main() -> Result<()> {
// println!("Hello, world!");
//
// let manager = Manager::new().await?;
// let mut adapter_list = manager.adapters().await?;
// if adapter_list.is_empty() {
// bail!("Error: no bluetooth adapters found.");
// }
// println!("Found {} bluetooth adapters:", adapter_list.len());
// for adapter in &adapter_list {
// println!("\t{}", adapter.adapter_info().await?);
// }
// let adapter: Adapter = adapter_list.pop().expect("No adapters after check!?");
// drop(adapter_list);
//
// let mut events = adapter.events().await?;
// adapter.start_scan(ScanFilter::default()).await?;
//
// while let Some(event) = events.next().await {
// match event {
// CentralEvent::DeviceDiscovered(id) => {
// println!("Discov: {:?}", id);
// }
// CentralEvent::DeviceUpdated(id) => println!("Update: {:?}", id),
// CentralEvent::DeviceConnected(id) => println!("Connec: {:?}", id),
// CentralEvent::DeviceDisconnected(id) => println!("Discon: {:?}", id),
// CentralEvent::ManufacturerDataAdvertisement {
// id,
// manufacturer_data,
// } => println!("ManAdv: {:?}", id),
// CentralEvent::ServiceDataAdvertisement { id, service_data } => {
// println!("DatAdv: {:?}", id)
// }
// CentralEvent::ServicesAdvertisement { id, services } => println!("SrvAdv: {:?}", id),
// }
// }
//
// Ok(())
//}
use std::io::{self, Stdout};
use anyhow::{Context, Result};
use crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::prelude::*;
mod app;
use app::App;
fn main() -> anyhow::Result<()> {
let mut a = App::new();
let mut term = setup_term()?;
let res = a.run(&mut term);
restore_term(&mut term)?;
res
}
/*
fn render_app(frame: &mut Frame) {
let greeting = widgets::Paragraph::new("Hello World!");
frame.render_widget(greeting, frame.size());
}
fn run(term: &mut Terminal<CrosstermBackend<Stdout>>, mut app: App) -> Result<()> {
loop {
todo!()
}
}
*/
fn setup_term() -> Result<Terminal<CrosstermBackend<Stdout>>> {
let mut stdout = io::stdout();
enable_raw_mode().context("Failed to enable raw mode")?;
execute!(stdout, EnterAlternateScreen).context("Failed to enter alternate screen")?;
Terminal::new(CrosstermBackend::new(stdout)).context("Failed to create terminal")
}
// TODO restore term shouldn't fail early and should continue to attempt to restore the terminal even in
// the event of one or two errors
fn restore_term(term: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
disable_raw_mode().context("Failed to disable raw mode")?;
execute!(term.backend_mut(), LeaveAlternateScreen)
.context("Failed to leave alternate screen")?;
term.show_cursor().context("Failed to show cursor")
}