Skip to content

Commit

Permalink
Add initial firmware download UI!
Browse files Browse the repository at this point in the history
  • Loading branch information
rnd-ash committed May 19, 2023
1 parent 69b709b commit ee5f1df
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 22 deletions.
12 changes: 8 additions & 4 deletions backend/src/hw/firmware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,7 @@ impl From<std::io::Error> for FirmwareLoadError {
}
}

pub fn load_binary(path: String) -> FirwmareLoadResult<Firmware> {
let mut f = File::open(path)?;
let mut buf = Vec::new();
f.read_to_end(&mut buf)?;
pub fn load_binary(buf: Vec<u8>) -> FirwmareLoadResult<Firmware> {
// Todo find a nicer way to do this!
let mut header_start_idx = 0;
loop {
Expand All @@ -114,3 +111,10 @@ pub fn load_binary(path: String) -> FirwmareLoadResult<Firmware> {
.unwrap();
Ok(Firmware { raw: buf, header })
}

pub fn load_binary_from_path(path: String) -> FirwmareLoadResult<Firmware> {
let mut f = File::open(path)?;
let mut buf = Vec::new();
f.read_to_end(&mut buf)?;
load_binary(buf)
}
6 changes: 5 additions & 1 deletion config_app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ nom="7.1.1"
plotters-backend="0.3.4"
plotters={version="0.3.4", default_features = false, features=["surface_series"]}
packed_struct="0.10.0"
ehttp="0.2.0"
ehttp="0.2.0"
octocrab = {git = "https://github.com/XAMPPRocky/octocrab", commit="7061b48c9bb799cb7effa0ecb8fd366a23f13de2"}
tokio = { version = "1.17.0", features = ["full"] }
zip="0.6.6"
curl = "0.4.43"
1 change: 1 addition & 0 deletions config_app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn main() {
let mut app = window::MainWindow::new();
app.add_new_page(Box::new(Launcher::new()));
let mut native_options = NativeOptions::default();
native_options.vsync = true;
native_options.icon_data = Some(IconData {
rgba: icon.into_raw(),
width: icon_w,
Expand Down
196 changes: 179 additions & 17 deletions config_app/src/ui/updater.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use std::{sync::{Arc, RwLock}, time::Instant, path::PathBuf, fs::File, io::Write};
use std::{sync::{Arc, RwLock}, time::Instant, path::PathBuf, fs::File, io::{Write, BufReader, Cursor}};

use backend::{diag::{Nag52Diag, flash::PartitionInfo}, hw::firmware::{Firmware, load_binary, FirmwareHeader}};
use eframe::egui;
use backend::{diag::{Nag52Diag, flash::PartitionInfo, DataState}, hw::firmware::{Firmware, load_binary, FirmwareHeader, load_binary_from_path}};
use curl::easy::{Easy, List};
use eframe::egui::{self, ScrollArea};
use nfd::Response;
use octocrab::{models::repos::Release, repos::releases::ListReleasesBuilder};
use tokio::runtime::Runtime;

use crate::window::{InterfacePage, PageAction};

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum CurrentFlashState {
None,
Download,
Unzip,
Prepare,
Read { start_addr: u32, current: u32, total: u32 },
Write { start_addr: u32, current: u32, total: u32 },
Expand Down Expand Up @@ -44,24 +49,56 @@ impl CurrentFlashState {

pub struct UpdatePage {
nag: Nag52Diag,
fw: Option<Firmware>,
fw: Arc<RwLock<Option<Firmware>>>,
status: Arc<RwLock<CurrentFlashState>>,
flash_start: Option<Instant>,
coredump: Option<PartitionInfo>,
old_fw: Option<(FirmwareHeader, PartitionInfo)>,
releases: Arc<RwLock<DataState<Vec<Release>>>>,
checked_unstable: bool,
selected_release: Option<Release>
}

impl UpdatePage {
pub fn new(mut nag: Nag52Diag) -> Self {
let coredump_info = nag.get_coredump_flash_info().ok();
let curr_fw_info = nag.get_running_fw_info().ok().zip(nag.get_running_partition_flash_info().ok());

let fw_list = Arc::new(RwLock::new(DataState::Unint));
let fw_list_c = fw_list.clone();

std::thread::spawn(move|| {
let rt = Runtime::new().unwrap();
match rt.block_on(async {
octocrab::instance().repos("rnd-ash", "ultimate-nag52-fw")
.releases()
.list()
.send()
.await
}) {
Ok(l) => {
let mut r_list = vec![];
for release in l {
r_list.push(release);
}
*fw_list_c.write().unwrap() = DataState::LoadOk(r_list);
},
Err(e) => {
*fw_list_c.write().unwrap() = DataState::LoadErr(format!("{:?}", e));
}
}
});

Self{
nag,
fw: None,
fw: Arc::new(RwLock::new(None)),
status: Arc::new(RwLock::new(CurrentFlashState::None)),
flash_start: None,
coredump: coredump_info,
old_fw: curr_fw_info
old_fw: curr_fw_info,
releases: fw_list,
checked_unstable: false,
selected_release: None
}
}
}
Expand Down Expand Up @@ -89,12 +126,8 @@ fn make_fw_info(ui: &mut egui::Ui, id: &str, fw: &FirmwareHeader, part_info: Opt
ui.label(fw.get_idf_version());
ui.end_row();

ui.label("Build date");
ui.label(fw.get_date());
ui.end_row();

ui.label("Build time");
ui.label(fw.get_time());
ui.label(fw.get_build_timestamp().map(|f| f.to_string()).unwrap_or("Unknown".into()));
ui.end_row();
});
}
Expand Down Expand Up @@ -138,11 +171,111 @@ impl InterfacePage for UpdatePage {
}
ui.separator();
ui.heading("Update to new Firmware");


let r = self.releases.read().unwrap().clone();
match r {
DataState::LoadOk(release_list) => {
fn release_to_string(r: &Release) -> String {
let date = r.clone().created_at.map(|x| x.to_string()).unwrap_or("Unknown date".into());
let rel = r.clone().name.unwrap_or(r.clone().tag_name);
format!("{} at {}", rel, date)
}

ui.checkbox(&mut self.checked_unstable, "Show unstable releases");
egui::ComboBox::from_label("Select release")
.width(500.0)
.selected_text(&self.selected_release.clone().map(|x| release_to_string(&x)).unwrap_or("None".into()))
.show_ui(ui, |cb_ui| {
for r in release_list {
if !self.checked_unstable && (r.prerelease || !r.tag_name.starts_with("main")) {
continue;
}


cb_ui.selectable_value(
&mut self.selected_release,
Some(r.clone()),
release_to_string(&r)
);
}
}
);
if let Some(rel) = &self.selected_release {
ui.hyperlink_to("Show on GitHub", format!("https://github.com{}", rel.html_url.path()));
let fw = rel.assets.iter().find(|x| x.name.ends_with(".bin"));
let elf = rel.assets.iter().find(|x| x.name.ends_with(".elf"));


if let Some(fw) = fw {
let url = format!("https://api.github.com{}",fw.url.path());
if ui.button("Download firmware").clicked() {
let state_c = self.status.clone();
let fw_c = self.fw.clone();
std::thread::spawn(move|| {
*state_c.write().unwrap() = CurrentFlashState::Download;
let mut buffer: Vec<u8> = Vec::new();
let mut easy = Easy::new();
let mut list = List::new();
list.append("Accept: application/octet-stream").unwrap();
easy.http_headers(list).unwrap();
easy.useragent("request").unwrap();
easy.follow_location(true).unwrap();
easy.url(&url).unwrap();
{
let mut transfer = easy.transfer();
let _ = transfer.write_function(|data| {
buffer.extend_from_slice(data);
Ok(data.len())
});
let _ = transfer.perform();
}

let code = easy.response_code().unwrap_or(0);
if code == 200 || code == 302 {
// Try and load FW from here
match load_binary(buffer) {
Ok(bin) => {
*fw_c.write().unwrap() = Some(bin);
*state_c.write().unwrap() = CurrentFlashState::None;
}
Err(e) => {
*state_c.write().unwrap() = CurrentFlashState::Failed(format!("Firmware was invalid: {:?}", e));
}
}
} else {
*state_c.write().unwrap() = CurrentFlashState::Failed(format!("Firmware download response code was {code}"));
}
});
}
}

if let Some(elf) = elf {
if ui.button("Download debug elf file").clicked() {
return PageAction::SendNotification { text: format!("Todo. Debugger UI!"), kind: egui_toast::ToastKind::Info }
}
}
}
},
DataState::Unint => {
ui.horizontal(|row| {
row.spinner();
row.label("Querying release list");
});
},
DataState::LoadErr(e) => {
ui.label(format!("Could not query release list: {:?}", e));
},
}
//if let Some(sel_rel) = &self.selected_release {
// ui.hyperlink_to(label, url)
//}

if ui.button("Load FW").clicked() {
match nfd::open_file_dialog(Some("bin"), None) {
Ok(Response::Okay(path)) => {
match load_binary(path) {
Ok(f) => self.fw = Some(f),
match load_binary_from_path(path) {
Ok(f) => *self.fw.write().unwrap() = Some(f),
Err(e) => {
eprintln!("E loading binary! {:?}", e)
}
Expand All @@ -151,11 +284,34 @@ impl InterfacePage for UpdatePage {
_ => {}
}
}
if let Some(fw) = &self.fw {
let c_fw = self.fw.clone().read().unwrap().clone();
if let Some(fw) = &c_fw {
make_fw_info(ui, "nfw",&fw.header, None);
if ui.button("Flash new FW").clicked() {
let mut flash = false;
let mut disclaimer = false;
if let Some(new_ts) = fw.header.get_build_timestamp() {
if let Some(old_ts) = self.old_fw.map(|f| f.0.get_build_timestamp()).flatten() {
if old_ts > new_ts {
ui.strong("WARNING. The new firmware is older than the current firmware! This can cause bootloops!");
ui.hyperlink_to("See reverting to old FW versions", "docs.ultiamte-nag52.net");
disclaimer = true;
}
}
}
if (!fw.header.get_version().contains("main") || fw.header.get_version().contains("dirty")) && self.old_fw.map(|x| x.0.get_version().contains("main")).unwrap_or(true) {
ui.strong("WARNING. You are about to flash potentially unstable firmware. Proceed with caution!");
disclaimer = true;
}
let text = match disclaimer {
true => "I have read the warnings. Proceed with flashing",
false => "Flash new FW",
};
if ui.button(text).clicked() {
flash = true;
}
if flash {
let mut ng = self.nag.clone();
let fw_c = self.fw.clone().unwrap();
let fw_c = c_fw.clone().unwrap();
let state_c = self.status.clone();
std::thread::spawn(move || {
*state_c.write().unwrap() = CurrentFlashState::Prepare;
Expand Down Expand Up @@ -258,7 +414,13 @@ impl InterfacePage for UpdatePage {
CurrentFlashState::None => (1.0, "Idle".to_string()),
CurrentFlashState::Verify => (1.0, "Verifying".to_string()),
CurrentFlashState::Completed(s) => (1.0, s),
CurrentFlashState::Failed(s) => (1.0, s)
CurrentFlashState::Failed(s) => (1.0, s),
CurrentFlashState::Download => {
(0.0, format!("Downloading firmware"))
},
CurrentFlashState::Unzip => {
(0.0, "Unzipping firmware".to_string())
},
};
ui.add(egui::widgets::ProgressBar::new(progress_percent).animate(true).show_percentage());
if state.is_tx_rx() {
Expand Down

0 comments on commit ee5f1df

Please sign in to comment.