mirror of
https://github.com/ferrous-systems/embedded-trainings-2020.git
synced 2025-02-08 22:02:20 +00:00
replace tools workspace with xtask tasks
This commit is contained in:
parent
80aeec1551
commit
b08a82bacd
14 changed files with 182 additions and 170 deletions
2
advanced/.cargo/config.toml
Normal file
2
advanced/.cargo/config.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[alias]
|
||||
xtask = "run --manifest-path ../xtask/Cargo.toml --"
|
2
beginner/.cargo/config.toml
Normal file
2
beginner/.cargo/config.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[alias]
|
||||
xtask = "run --manifest-path ../xtask/Cargo.toml --"
|
|
@ -1,7 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"change-channel",
|
||||
"dongle-flash",
|
||||
"serial-term",
|
||||
"usb-list",
|
||||
]
|
|
@ -1,12 +0,0 @@
|
|||
[package]
|
||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "change-channel"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.27"
|
||||
consts = { path = "../../advanced/common/consts" }
|
||||
hidapi = "1.2.2"
|
||||
pids = { path = "../../common/pids" }
|
|
@ -1,33 +0,0 @@
|
|||
use std::env;
|
||||
|
||||
use anyhow::{anyhow, bail, ensure};
|
||||
use hidapi::HidApi;
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
let args = env::args()
|
||||
.skip(1) // skip program name
|
||||
.collect::<Vec<_>>();
|
||||
ensure!(!args.is_empty(), "expected exactly one argument");
|
||||
|
||||
let api = HidApi::new()?;
|
||||
let dev = api
|
||||
.device_list()
|
||||
.filter(|dev| dev.vendor_id() == consts::VID && check_pid(dev.product_id()))
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("device not found"))?
|
||||
.open_device(&api)?;
|
||||
|
||||
let chan = args[0].parse::<u8>()?;
|
||||
if chan < 11 || chan > 26 {
|
||||
bail!("channel is out of range (`11..=26`)")
|
||||
}
|
||||
const REPORT_ID: u8 = 0;
|
||||
dev.write(&[REPORT_ID, chan])?;
|
||||
println!("requested channel change to channel {}", chan);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_pid(pid: u16) -> bool {
|
||||
pid == pids::LOOPBACK || pid == pids::PUZZLE
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
[package]
|
||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "dongle-flash"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.31"
|
||||
ihex = "1.1.2"
|
||||
serialport = "3.3.0"
|
||||
tempfile = "3.1.0"
|
||||
xmas-elf = "0.7.0"
|
|
@ -1,12 +0,0 @@
|
|||
[package]
|
||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "serial-term"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.30"
|
||||
consts = { path = "../../advanced/common/consts" }
|
||||
ctrlc = "3.1.4"
|
||||
serialport = "3.3.0"
|
|
@ -1,52 +0,0 @@
|
|||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{
|
||||
io::{self, Read as _, Write as _},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use serialport::SerialPortType;
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
let mut once = true;
|
||||
let dongle = loop {
|
||||
if let Some(dongle) = serialport::available_ports()?
|
||||
.into_iter()
|
||||
.filter(|info| match &info.port_type {
|
||||
SerialPortType::UsbPort(usb) => usb.vid == consts::VID,
|
||||
_ => false,
|
||||
})
|
||||
.next()
|
||||
{
|
||||
break dongle;
|
||||
} else if once {
|
||||
once = false;
|
||||
|
||||
eprintln!("(waiting for the Dongle to be connected)");
|
||||
}
|
||||
};
|
||||
|
||||
let mut port = serialport::open(&dongle.port_name)?;
|
||||
|
||||
static CONTINUE: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
// properly close the serial device on Ctrl-C
|
||||
ctrlc::set_handler(|| CONTINUE.store(false, Ordering::Relaxed))?;
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let mut read_buf = [0; 64];
|
||||
while CONTINUE.load(Ordering::Relaxed) {
|
||||
if port.bytes_to_read()? != 0 {
|
||||
let n = port.read(&mut read_buf)?;
|
||||
stdout.write_all(&read_buf[..n])?;
|
||||
stdout.flush()?;
|
||||
} else {
|
||||
// time span between two consecutive FS USB packets
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("(closing the serial port)");
|
||||
Ok(())
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "usb-list"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
consts = { path = "../../advanced/common/consts" }
|
||||
pids = { path = "../../common/pids" }
|
||||
rusb = "0.5.5"
|
|
@ -1,19 +0,0 @@
|
|||
use std::error::Error;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
for dev in rusb::devices()?.iter() {
|
||||
let desc = dev.device_descriptor()?;
|
||||
let suffix = match (desc.vendor_id(), desc.product_id()) {
|
||||
(0x1366, 0x1015) => " <- J-Link on the nRF52840 Development Kit",
|
||||
(0x1915, 0x521f) => " <- nRF52840 Dongle (in bootloader mode)",
|
||||
(0x2020, pids::LOOPBACK) => " <- nRF52840 Dongle (loopback.hex)",
|
||||
(0x2020, pids::PUZZLE) => " <- nRF52840 Dongle (puzzle.hex)",
|
||||
(consts::VID, consts::PID) => " <- nRF52840 on the nRF52840 Development Kit",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
println!("{:?}{}", dev, suffix);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
17
xtask/Cargo.toml
Normal file
17
xtask/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
|
||||
edition = "2018"
|
||||
name = "xtask"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
color-eyre = "0.5.10"
|
||||
consts = { path = "../advanced/common/consts" }
|
||||
ctrlc = "3.1.4"
|
||||
hidapi = "1.2.2"
|
||||
ihex = "1.1.2"
|
||||
pids = { path = "../common/pids" }
|
||||
rusb = "0.5.5"
|
||||
serialport = "3.3.0"
|
||||
tempfile = "3.2.0"
|
||||
xmas-elf = "0.7.0"
|
1
xtask/src/config.rs
Normal file
1
xtask/src/config.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub const DONGLE_PATH: &[&str] = &["boards", "dongle"];
|
58
xtask/src/main.rs
Normal file
58
xtask/src/main.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
#![deny(warnings)]
|
||||
|
||||
mod config;
|
||||
mod tasks;
|
||||
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
fn main() -> color_eyre::Result<()> {
|
||||
color_eyre::install()?;
|
||||
|
||||
// first arg is the name of the executable; skip it
|
||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let args = args.iter().map(|arg| &arg[..]).collect::<Vec<_>>();
|
||||
|
||||
match &args[..] {
|
||||
["change-channel", channel] => tasks::change_channel(channel),
|
||||
["dongle-flash", hex] => tasks::dongle_flash(hex),
|
||||
["serial-term"] => tasks::serial_term(),
|
||||
["usb-list"] => tasks::usb_list(),
|
||||
_ => {
|
||||
eprintln!(
|
||||
"cargo xtask
|
||||
Workshop-specific tools
|
||||
|
||||
USAGE:
|
||||
cargo xtask [COMMAND]
|
||||
|
||||
COMMANDS:
|
||||
change-channel [NUMBER] instructs the nRF Dongle to listen to a different radio channel
|
||||
dongle-flash [PATH] flashes the hex file on the dongle (NOTE PATH is relative to the boards/dongle directory)
|
||||
serial-term displays the log output of the Dongle
|
||||
usb-list list all connected USB devices; highlights workshop devices
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// changes directory into the given path, relative to the root of the repository
|
||||
fn cd(segments: &[&str]) -> color_eyre::Result<()> {
|
||||
let mut path = repository_root()?;
|
||||
for segment in segments {
|
||||
path.push(segment);
|
||||
}
|
||||
env::set_current_dir(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn repository_root() -> color_eyre::Result<PathBuf> {
|
||||
// path to this crate (the directory that contains this crate's Cargo.toml)
|
||||
Ok(PathBuf::from(env::var("CARGO_MANIFEST_DIR")?)
|
||||
// from there go one level up
|
||||
.parent()
|
||||
.unwrap()
|
||||
.to_owned())
|
||||
}
|
|
@ -1,11 +1,16 @@
|
|||
use core::convert::TryFrom;
|
||||
use std::{
|
||||
env, fs,
|
||||
convert::TryFrom,
|
||||
fs,
|
||||
io::{self, Write as _},
|
||||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure};
|
||||
use color_eyre::eyre::{anyhow, bail, Report};
|
||||
use hidapi::HidApi;
|
||||
use ihex::record::Record;
|
||||
use serialport::SerialPortType;
|
||||
use tempfile::TempDir;
|
||||
|
@ -14,13 +19,37 @@ use xmas_elf::{
|
|||
ElfFile,
|
||||
};
|
||||
|
||||
const VID: u16 = 0x1915;
|
||||
const PID: u16 = 0x521f;
|
||||
use crate::config;
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
let args = env::args().skip(1 /* program name */).collect::<Vec<_>>();
|
||||
pub fn change_channel(channel: &str) -> color_eyre::Result<()> {
|
||||
fn check_pid(pid: u16) -> bool {
|
||||
pid == pids::LOOPBACK || pid == pids::PUZZLE
|
||||
}
|
||||
|
||||
ensure!(args.len() == 1, "expected exactly one argument");
|
||||
let api = HidApi::new()?;
|
||||
let dev = api
|
||||
.device_list()
|
||||
.filter(|dev| dev.vendor_id() == consts::VID && check_pid(dev.product_id()))
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("device not found"))?
|
||||
.open_device(&api)?;
|
||||
|
||||
let chan = channel.parse::<u8>()?;
|
||||
if chan < 11 || chan > 26 {
|
||||
bail!("channel is out of range (`11..=26`)")
|
||||
}
|
||||
const REPORT_ID: u8 = 0;
|
||||
dev.write(&[REPORT_ID, chan])?;
|
||||
println!("requested channel change to channel {}", chan);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn dongle_flash(hex: &str) -> color_eyre::Result<()> {
|
||||
const VID: u16 = 0x1915;
|
||||
const PID: u16 = 0x521f;
|
||||
|
||||
crate::cd(config::DONGLE_PATH)?;
|
||||
|
||||
let dongle = serialport::available_ports()?
|
||||
.into_iter()
|
||||
|
@ -35,7 +64,7 @@ connect the Dongle to your laptop or PC and press the reset button to put it in
|
|||
if the red LED was blinking and you got this message then the device wasn't correctly enumerated; remove it and try again")
|
||||
})?;
|
||||
|
||||
let path = Path::new(&args[0]);
|
||||
let path = Path::new(hex);
|
||||
let filename = Path::new(
|
||||
path.file_name()
|
||||
.ok_or_else(|| anyhow!("{} has no file name", path.display()))?,
|
||||
|
@ -52,13 +81,13 @@ if the red LED was blinking and you got this message then the device wasn't corr
|
|||
// here we map the ELF loadable segments -- these correspond to sections like `.text`, `.rodata`
|
||||
// and `.data` (initial values) -- to ihex records
|
||||
let bytes = fs::read(path)?;
|
||||
let elf_file = ElfFile::new(&bytes).map_err(anyhow::Error::msg)?;
|
||||
let elf_file = ElfFile::new(&bytes).map_err(Report::msg)?;
|
||||
let mut records = vec![];
|
||||
for ph in elf_file.program_iter() {
|
||||
if ph.get_type() == Ok(Type::Load) {
|
||||
let start = ph.physical_addr();
|
||||
|
||||
match ph.get_data(&elf_file).map_err(anyhow::Error::msg)? {
|
||||
match ph.get_data(&elf_file).map_err(Report::msg)? {
|
||||
SegmentData::Undefined(bytes) => {
|
||||
// this is what `objcopy -O ihex` uses and it works with `nrfutil`
|
||||
const RECORD_SIZE: usize = 16;
|
||||
|
@ -148,3 +177,65 @@ if the red LED was blinking and you got this message then the device wasn't corr
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn serial_term() -> color_eyre::Result<()> {
|
||||
let mut once = true;
|
||||
let dongle = loop {
|
||||
if let Some(dongle) = serialport::available_ports()?
|
||||
.into_iter()
|
||||
.filter(|info| match &info.port_type {
|
||||
SerialPortType::UsbPort(usb) => usb.vid == consts::VID,
|
||||
_ => false,
|
||||
})
|
||||
.next()
|
||||
{
|
||||
break dongle;
|
||||
} else if once {
|
||||
once = false;
|
||||
|
||||
eprintln!("(waiting for the Dongle to be connected)");
|
||||
}
|
||||
};
|
||||
|
||||
let mut port = serialport::open(&dongle.port_name)?;
|
||||
|
||||
static CONTINUE: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
// properly close the serial device on Ctrl-C
|
||||
ctrlc::set_handler(|| CONTINUE.store(false, Ordering::Relaxed))?;
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let mut read_buf = [0; 64];
|
||||
while CONTINUE.load(Ordering::Relaxed) {
|
||||
if port.bytes_to_read()? != 0 {
|
||||
let n = port.read(&mut read_buf)?;
|
||||
stdout.write_all(&read_buf[..n])?;
|
||||
stdout.flush()?;
|
||||
} else {
|
||||
// time span between two consecutive FS USB packets
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("(closing the serial port)");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn usb_list() -> color_eyre::Result<()> {
|
||||
for dev in rusb::devices()?.iter() {
|
||||
let desc = dev.device_descriptor()?;
|
||||
let suffix = match (desc.vendor_id(), desc.product_id()) {
|
||||
(0x1366, 0x1015) => " <- J-Link on the nRF52840 Development Kit",
|
||||
(0x1915, 0x521f) => " <- nRF52840 Dongle (in bootloader mode)",
|
||||
(0x2020, pids::LOOPBACK) => " <- nRF52840 Dongle (loopback.hex)",
|
||||
(0x2020, pids::PUZZLE) => " <- nRF52840 Dongle (puzzle.hex)",
|
||||
(consts::VID, consts::PID) => " <- nRF52840 on the nRF52840 Development Kit",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
println!("{:?}{}", dev, suffix);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue