tudelft_serial_upload/
upload.rs1use crate::serial::Serial;
2use crate::{selector, PortSelector};
3use color_eyre::eyre::{bail, eyre, Context};
4use color_eyre::Result;
5use std::fs::read;
6use std::path::{Path, PathBuf};
7use std::process::{exit, Command};
8
9fn copy_object(source: &Path, target: &Path) -> Result<()> {
10 if Command::new("rust-objcopy").output().is_err() {
11 bail!(
12 "rust-objcopy not found, try installing cargo-binutils or refer to the course website"
13 );
14 }
15
16 let op = Command::new("rust-objcopy")
17 .arg("-O")
18 .arg("binary")
19 .arg(source)
20 .arg(target)
21 .output()
22 .wrap_err("failed to run rust-objcopy")?;
23
24 println!("creating binary file at {target:?}");
25
26 if !op.status.success() {
27 bail!(
28 "running rust-objcopy failed: {}",
29 String::from_utf8_lossy(&op.stderr)
30 );
31 }
32
33 Ok(())
34}
35
36fn read_file(file: &Path) -> Result<Vec<u8>> {
37 let mut target = file.to_path_buf();
38 target.set_extension("bin");
39
40 println!("converting elf file to bin file");
41 copy_object(file, &target)?;
42
43 println!("reading binary file");
44 read(target).wrap_err("failed to read converted binary file to send to board")
45}
46
47pub fn upload_file_or_stop(port: PortSelector, file: Option<impl AsRef<Path>>) -> PathBuf {
53 if let Some(file) = file {
54 match read_file(file.as_ref())
55 .wrap_err_with(|| format!("failed to read from file {:?}", file.as_ref()))
56 {
57 Ok(i) => upload_or_stop(port, i, false),
58 Err(e) => {
59 eprintln!("{e:?}");
60 exit(1);
61 }
62 }
63 } else {
64 upload_or_stop(port, [], true)
65 }
66}
67
68pub fn upload_file(port: PortSelector, file: Option<impl AsRef<Path>>) -> Result<PathBuf> {
74 upload(
75 port,
76 file.as_ref()
77 .map(|f| {
78 read_file(f.as_ref())
79 .wrap_err_with(|| format!("failed to read from file {:?}", f.as_ref()))
80 })
81 .transpose()?
82 .unwrap_or_default(),
83 file.is_none(),
84 )
85}
86
87pub fn upload_or_stop(port: PortSelector, file: impl AsRef<[u8]>, dry_run: bool) -> PathBuf {
94 match upload(port, file.as_ref(), dry_run) {
95 Err(e) => {
96 eprintln!("{e:?}");
97 exit(1);
98 }
99 Ok(i) => i,
100 }
101}
102
103pub fn upload(port: PortSelector, file: impl AsRef<[u8]>, dry_run: bool) -> Result<PathBuf> {
110 upload_internal(port, file.as_ref(), dry_run)
111}
112
113fn upload_internal(port: PortSelector<'_>, file: &[u8], dry_run: bool) -> Result<PathBuf> {
114 if dry_run && matches!(port, PortSelector::SearchAll) {
115 bail!("can't use dry_run in SearchAll mode");
116 }
117
118 let (ports_to_try, stop_after_first_error): (Vec<Result<Serial>>, bool) = match port {
119 PortSelector::SearchFirst => (
120 selector::all_serial_ports()
121 .map(PathBuf::from)
122 .map(Serial::open)
123 .collect(),
124 true,
125 ),
126 PortSelector::SearchAll => (
127 selector::all_serial_ports()
128 .map(PathBuf::from)
129 .map(Serial::open)
130 .collect(),
131 false,
132 ),
133 PortSelector::ChooseInteractive => (
134 vec![Serial::open(PathBuf::from(selector::choose_interactive()?))],
135 true,
136 ),
137 PortSelector::Named(n) => (vec![Serial::open(Path::new(n).to_path_buf())], false),
138 PortSelector::AutoManufacturer => (
139 vec![Serial::open(PathBuf::from(
140 selector::find_available_serial_port_by_id()?,
141 ))],
142 true,
143 ),
144 };
145
146 let mut errors = Vec::new();
147 let num_ports = ports_to_try.len();
148
149 for i in ports_to_try {
150 let mut port = match i {
151 Ok(i) => i,
152 Err(e) => {
153 if stop_after_first_error || num_ports == 1 {
154 return Err(e);
155 }
156 eprintln!("WARNING: {e}");
157 errors.push(e);
158 continue;
159 }
160 };
161
162 if dry_run {
163 return Ok(port.path);
164 }
165
166 if let Err(e) = port
167 .try_do_upload(file)
168 .wrap_err_with(|| format!("failed to upload to port {:?}", port.path))
169 {
170 if stop_after_first_error || num_ports == 1 {
171 return Err(e);
172 }
173 eprintln!("WARNING: {e}");
174 errors.push(e);
175 continue;
176 }
177 return Ok(port.path);
178 }
179
180 Err(eyre!(
181 "uploading failed because none of the ports tried worked (see previous warnings)"
182 ))
183}