Skip to content

Commit

Permalink
Support android version
Browse files Browse the repository at this point in the history
Ref #4
  • Loading branch information
KoffeinFlummi committed Nov 5, 2023
1 parent 2581ed0 commit be1b1ad
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 91 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ rand = { version = "0.8", default-features = false, features = ["std", "std_rng"
[target.'cfg(target_arch = "aarch64")'.dependencies]
home = "0.5" # No home directories in web assembly
indicatif = "0.17"
rand = { version = "0.8", default-features = false, features = ["small_rng", "getrandom"] }
rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] }

# Web Assembly dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies]
Expand Down
2 changes: 1 addition & 1 deletion src/data_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub mod serial;
pub mod simulation;

pub use log_file::LogFileDataSource;
pub use serial::SerialDataSource;
pub use serial::*;
pub use simulation::SimulationDataSource;

/// Trait shared by all data sources.
Expand Down
41 changes: 36 additions & 5 deletions src/data_source/serial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ pub const BAUD_RATE: u32 = 115_200;
pub const MESSAGE_TIMEOUT: Duration = Duration::from_millis(500);
pub const HEARTBEAT_INTERVAL: Duration = Duration::from_millis(500);

// For Android, the Java wrapper has to handle the actual serial port and
// we use these questionable methods to pass the data in via JNI
#[cfg(target_os="android")]
pub static mut DOWNLINK_MESSAGE_RECEIVER: Option<Receiver<DownlinkMessage>> = None;
#[cfg(target_os="android")]
pub static mut UPLINK_MESSAGE_SENDER: Option<Sender<UplinkMessage>> = None;
#[cfg(target_os="android")]
pub static mut SERIAL_STATUS_RECEIVER: Option<Receiver<SerialStatus>> = None;

/// The current state of our downlink monitoring thread
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SerialStatus {
Expand Down Expand Up @@ -130,7 +139,7 @@ pub fn find_serial_port() -> Option<String> {
/// Continuously monitors for connected USB serial devices and connects to them.
/// Run in a separate thread using `spawn_downlink_monitor`.
#[cfg(not(target_arch = "wasm32"))] // TODO: serial ports on wasm?
fn downlink_monitor(
pub fn downlink_monitor(
serial_status_tx: Sender<(SerialStatus, Option<String>)>,
mut downlink_tx: Sender<DownlinkMessage>,
mut uplink_rx: Receiver<UplinkMessage>,
Expand Down Expand Up @@ -255,16 +264,31 @@ impl DataSource for SerialDataSource {
fn update(&mut self, _ctx: &egui::Context) {
self.message_receipt_times.retain(|(i, _)| i.elapsed() < Duration::from_millis(1000));

for (status, port) in self.serial_status_rx.try_iter() {
#[cfg(target_os="android")]
for status in unsafe { SERIAL_STATUS_RECEIVER.as_mut().unwrap().try_iter() } {
self.serial_status = status;
self.serial_port = Some("".to_owned());

if self.serial_status == SerialStatus::Connected {
self.send(UplinkMessage::ApplyLoRaSettings(self.lora_settings.clone())).unwrap();
}
}

#[cfg(not(target_os="android"))]
for (status, port) in self.serial_status_rx.try_iter().collect::<Vec<_>>().into_iter() {
self.serial_status = status;
self.serial_port = port;

if self.serial_status == SerialStatus::Connected {
self.uplink_tx.send(UplinkMessage::ApplyLoRaSettings(self.lora_settings.clone())).unwrap();
self.send(UplinkMessage::ApplyLoRaSettings(self.lora_settings.clone())).unwrap();
}
}

#[cfg(not(target_os = "android"))]
let msgs: Vec<_> = self.downlink_rx.try_iter().collect();
#[cfg(target_os = "android")]
let msgs: Vec<_> = unsafe { DOWNLINK_MESSAGE_RECEIVER.as_mut().unwrap().try_iter().collect() };

for msg in msgs.into_iter() {
self.write_to_telemetry_log(&msg);

Expand Down Expand Up @@ -309,11 +333,18 @@ impl DataSource for SerialDataSource {
self.message_receipt_times.truncate(0);
}

#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(any(target_arch = "wasm32", target_os="android")))]
fn send(&mut self, msg: UplinkMessage) -> Result<(), SendError<UplinkMessage>> {
self.uplink_tx.send(msg)
}

#[cfg(target_os="android")]
fn send(&mut self, msg: UplinkMessage) -> Result<(), SendError<UplinkMessage>> {
unsafe {
UPLINK_MESSAGE_SENDER.as_mut().unwrap().send(msg)
}
}

#[cfg(target_arch = "wasm32")]
fn send(&mut self, msg: UplinkMessage) -> Result<(), SendError<UplinkMessage>> {
Ok(())
Expand Down Expand Up @@ -345,7 +376,7 @@ impl DataSource for SerialDataSource {

fn apply_settings(&mut self, settings: &AppSettings) {
self.lora_settings = settings.lora.clone();
self.uplink_tx.send(UplinkMessage::ApplyLoRaSettings(self.lora_settings.clone())).unwrap();
self.send(UplinkMessage::ApplyLoRaSettings(self.lora_settings.clone())).unwrap();
}

fn link_quality(&self) -> Option<f32> {
Expand Down
154 changes: 77 additions & 77 deletions src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub const ARCHIVE: [(&str, Option<&'static str>, Option<&'static str>); 5] = [
),
];

#[derive(Debug)]
enum ArchiveLoadProgress {
Progress((u64, u64)),
Complete(Vec<u8>),
Expand All @@ -88,8 +89,7 @@ pub struct Sam {
impl Sam {
/// Initialize the application, including the state objects for widgets
/// such as plots and maps.
pub fn init(cc: &eframe::CreationContext<'_>, settings: AppSettings, data_source: Box<dyn DataSource>) -> Self {
let ctx = &cc.egui_ctx;
pub fn init(ctx: &egui::Context, settings: AppSettings, data_source: Box<dyn DataSource>) -> Self {
let mut fonts = egui::FontDefinitions::default();
let roboto = egui::FontData::from_static(include_bytes!("../assets/fonts/RobotoMono-Regular.ttf"));
let lato = egui::FontData::from_static(include_bytes!("../assets/fonts/Overpass-Light.ttf"));
Expand Down Expand Up @@ -137,13 +137,13 @@ impl Sam {
let response = match reqwest::Client::new().get(url).send().await {
Ok(res) => res,
Err(e) => {
let _ = progress_sender.send(ArchiveLoadProgress::Error(e));
progress_sender.send(ArchiveLoadProgress::Error(e)).unwrap();
return;
}
};

let total_size = response.content_length().unwrap_or(0);
let _ = progress_sender.send(ArchiveLoadProgress::Progress((0, total_size)));
progress_sender.send(ArchiveLoadProgress::Progress((0, total_size))).unwrap();

let mut cursor = std::io::Cursor::new(Vec::with_capacity(total_size as usize));
let (mut progress, mut last_progress) = (0, 0);
Expand All @@ -159,13 +159,13 @@ impl Sam {
}
}
Err(e) => {
let _ = progress_sender.send(ArchiveLoadProgress::Error(e));
progress_sender.send(ArchiveLoadProgress::Error(e)).unwrap();
return;
}
}
}

let _ = progress_sender.send(ArchiveLoadProgress::Complete(cursor.into_inner()));
progress_sender.send(ArchiveLoadProgress::Complete(cursor.into_inner())).unwrap();
let duration = start.elapsed().as_secs_f32();
let mib = (total_size as f32) / 1024.0 / 1024.0;
info!("Downloaded {}MiB in {:.1}ms ({}MiB/s)", mib, duration * 1000.0, mib / duration);
Expand Down Expand Up @@ -310,6 +310,76 @@ impl Sam {
}

pub fn ui(&mut self, ctx: &egui::Context) {
#[cfg(feature = "profiling")]
puffin::profile_function!();
#[cfg(feature = "profiling")]
puffin::GlobalProfiler::lock().new_frame();
#[cfg(feature = "profiling")]
puffin_egui::profiler_window(ctx);

self.data_source.update(ctx);

// TODO: only send this if we know it's not a ground station?
if self.data_source.fc_settings().is_none() && self.data_source.vehicle_states().next().is_some() {
self.data_source.send(UplinkMessage::ReadSettings).unwrap();
}

if let Some(receiver) = self.archive_progress_receiver.as_ref() {
match receiver.try_recv() {
Ok(ArchiveLoadProgress::Progress(progress)) => {
self.archive_progress = Some(progress);
}
Ok(ArchiveLoadProgress::Complete(bytes)) => {
self.open_log_file(LogFileDataSource::from_bytes(
Some("".to_string()), // TODO: show title
bytes,
self.replay_logs,
));
self.archive_window_open = false;
self.archive_progress_receiver = None;
self.archive_progress = None;
}
Ok(ArchiveLoadProgress::Error(e)) => {
error!("{:?}", e); // TODO: show this visually
self.archive_progress_receiver = None;
self.archive_progress = None;
}
_ => {}
}
}

// Check for keyboard inputs for tab and flight mode changes
if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::F1)) {
self.tab = GuiTab::Launch;
} else if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::F2)) {
self.tab = GuiTab::Plot;
} else if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::F3)) {
self.tab = GuiTab::Configure;
}

let shortcut_mode = if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F5)) {
Some(FlightMode::Idle)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F6)) {
Some(FlightMode::HardwareArmed)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F7)) {
Some(FlightMode::Armed)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F8)) {
Some(FlightMode::Flight)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F9)) {
Some(FlightMode::RecoveryDrogue)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F10)) {
Some(FlightMode::RecoveryMain)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F11)) {
Some(FlightMode::Landed)
} else {
None
};

// Set new flight mode if keyboard shortcut was used
if let Some(fm) = shortcut_mode {
self.data_source.send_command(Command::SetFlightMode(fm)).unwrap();
}

// Redefine text_styles
let colors = ThemeColors::new(ctx);
let mut style = (*ctx.style()).clone();
Expand Down Expand Up @@ -526,76 +596,6 @@ impl Sam {
impl eframe::App for Sam {
/// Main draw method of the application
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
#[cfg(feature = "profiling")]
puffin::profile_function!();
#[cfg(feature = "profiling")]
puffin::GlobalProfiler::lock().new_frame();
#[cfg(feature = "profiling")]
puffin_egui::profiler_window(ctx);

self.data_source.update(ctx);

// TODO: only send this if we know it's not a ground station?
if self.data_source.fc_settings().is_none() && self.data_source.vehicle_states().next().is_some() {
self.data_source.send(UplinkMessage::ReadSettings).unwrap();
}

if let Some(receiver) = self.archive_progress_receiver.as_ref() {
match receiver.try_recv() {
Ok(ArchiveLoadProgress::Progress(progress)) => {
self.archive_progress = Some(progress);
}
Ok(ArchiveLoadProgress::Complete(bytes)) => {
self.open_log_file(LogFileDataSource::from_bytes(
Some("".to_string()), // TODO: show title
bytes,
self.replay_logs,
));
self.archive_window_open = false;
self.archive_progress_receiver = None;
self.archive_progress = None;
}
Ok(ArchiveLoadProgress::Error(e)) => {
error!("{:?}", e); // TODO: show this visually
self.archive_progress_receiver = None;
self.archive_progress = None;
}
_ => {}
}
}

// Check for keyboard inputs for tab and flight mode changes
if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::F1)) {
self.tab = GuiTab::Launch;
} else if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::F2)) {
self.tab = GuiTab::Plot;
} else if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::F3)) {
self.tab = GuiTab::Configure;
}

let shortcut_mode = if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F5)) {
Some(FlightMode::Idle)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F6)) {
Some(FlightMode::HardwareArmed)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F7)) {
Some(FlightMode::Armed)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F8)) {
Some(FlightMode::Flight)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F9)) {
Some(FlightMode::RecoveryDrogue)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F10)) {
Some(FlightMode::RecoveryMain)
} else if ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, Key::F11)) {
Some(FlightMode::Landed)
} else {
None
};

// Set new flight mode if keyboard shortcut was used
if let Some(fm) = shortcut_mode {
self.data_source.send_command(Command::SetFlightMode(fm)).unwrap();
}

// Draw UI
self.ui(ctx)
}
Expand All @@ -620,7 +620,7 @@ pub fn main(log_file: Option<PathBuf>) -> Result<(), Box<dyn std::error::Error>>
initial_window_size: Some(egui::vec2(1000.0, 700.0)),
..Default::default()
},
Box::new(|cc| Box::new(Sam::init(cc, app_settings, data_source))),
Box::new(|cc| Box::new(Sam::init(&cc.egui_ctx, app_settings, data_source))),
)?;

Ok(())
Expand Down
7 changes: 7 additions & 0 deletions src/gui/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,15 @@ fn tile_id(tile: &Tile) -> String {

#[cfg(not(target_arch = "wasm32"))]
fn load_tile_bytes(tile: &Tile, access_token: &String) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
#[cfg(not(target_os="android"))]
let project_dirs = directories::ProjectDirs::from("space", "tudsat", "sam").unwrap();
#[cfg(not(target_os="android"))]
let cache_dir = project_dirs.cache_dir();

// TODO: avoid hardcoding this
#[cfg(target_os="android")]
let cache_dir = std::path::Path::new("/data/user/0/space.tudsat.sam/cache");

if !cache_dir.exists() {
std::fs::create_dir_all(cache_dir)?;
}
Expand Down
4 changes: 2 additions & 2 deletions src/gui/tabs/configure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,12 @@ impl ConfigureTab {
data_source.send(UplinkMessage::WriteSettings(settings)).unwrap();
}

#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(any(target_arch = "wasm32", target_arch="aarch64")))]
if ui.add_enabled(data_source.fc_settings().is_some(), Button::new("🖹 Save to File")).clicked() {
save_fc_settings_file(&data_source.fc_settings().unwrap());
}

#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(any(target_arch = "wasm32", target_arch="aarch64")))]
if ui.add_enabled(data_source.fc_settings().is_some(), Button::new("🖹 Load from File")).clicked()
{
if let Some(settings) = open_fc_settings_file() {
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
// the application to silence warnings.
#[allow(dead_code)]
#[allow(unused_variables)]
mod data_source;
pub mod data_source;
#[allow(unused_imports)]
mod file;
#[allow(dead_code)]
#[allow(unused_imports)]
mod gui;
mod settings;
pub mod settings;
mod simulation;
mod state;
mod telemetry_ext;
Expand Down Expand Up @@ -60,7 +60,7 @@ impl WebHandle {
.start(
canvas_id,
eframe::WebOptions::default(),
Box::new(|cc| Box::new(Sam::init(cc, AppSettings::default(), data_source))),
Box::new(|cc| Box::new(Sam::init(&cc.egui_ctx, AppSettings::default(), data_source))),
)
.await
}
Expand Down
Loading

0 comments on commit be1b1ad

Please sign in to comment.