first commit
This commit is contained in:
commit
710522ba3d
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
||||
1290
Cargo.lock
generated
Normal file
1290
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal 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
147
src/app.rs
Normal 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
89
src/main.rs
Normal 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")
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user