Merge pull request #128 from ferrous-systems/xtask-tools

replace workshop specific tools with xtasks
This commit is contained in:
Jorge Aparicio 2021-02-01 14:42:29 +01:00 committed by GitHub
commit e26a8f50f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 251 additions and 295 deletions

2
.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[alias]
xtask = "run --manifest-path xtask/Cargo.toml --"

View File

@ -50,11 +50,12 @@ jobs:
sudo apt-get install libudev-dev libusb-1.0-0-dev
rustup +stable target add thumbv7em-none-eabihf
- name: build tools
working-directory: ./tools
- name: build xtask'ed tools
working-directory: ./xtask
run: |
cargo build --verbose
cargo fmt --all -- --check
cargo xtask
- name: build and fmt beginner/apps
working-directory: ./beginner/apps

View File

@ -0,0 +1,2 @@
[alias]
xtask = "run --manifest-path ../xtask/Cargo.toml --"

View File

@ -0,0 +1,2 @@
[alias]
xtask = "run --manifest-path ../xtask/Cargo.toml --"

View File

@ -1,8 +0,0 @@
[target.thumbv7em-none-eabihf]
runner = "dongle-flash"
rustflags = [
"-C", "link-arg=-Tlink.x",
]
[build]
target = "thumbv7em-none-eabihf"

View File

@ -0,0 +1,2 @@
[alias]
xtask = "run --manifest-path ../../xtask/Cargo.toml --"

View File

@ -94,13 +94,13 @@ $ arm-none-eabi-objcopy -O ihex ../target/thumbv7em-none-eabi/release/puzzle puz
Test the produced `puzzle.hex` file:
- flash it onto a dongle using the `dongle-flash` host tool. The green LED on the dongle should turn on
- run the `serial-term` host tool; you should see the following output. `deviceid` will be different
- flash it onto a dongle using `cargo xtask dongle-flash`. The green LED on the dongle should turn on
- run `cargo xtask serial-term`; you should see the following output. `deviceid` will be different
``` text
deviceid=d90eedf1978d5fd2 channel=25 TxPower=+8dBm app=puzzle.hex
```
- run the `radio-puzzle-solution` program on a DK; it should be able to decrypt the new secret
- run the `change-channel` host tool to test changing the Dongle's radio channel
- run `cargo xtask change-channel <some number between 11 and 26>` to test changing the Dongle's radio channel
- modify and re-run the `radio-puzzle-solution` program on a DK to solve the puzzle using a the channel you set in the previous step
### Generate `puzzle-nousb-*.hex`
@ -111,7 +111,7 @@ The procedure is similar to the one for generating the `puzzle.hex`. The differe
- you also need to change `const CHANNEL` in the `puzzle-nousb.rs` copy
- you need to produce one hex file per hard-coded radio channel.
Also test these `nousb` .hex files. Note that the green LED won't turn on when the dongle restarts! The green LED will toggle when a new packet is received and the blue LED will turn on when the decoded secret is received. Also, `change-channel` won't work with the `nousb` variants so you can skip that test.
Also test these `nousb` .hex files. Note that the green LED won't turn on when the dongle restarts! The green LED will toggle when a new packet is received and the blue LED will turn on when the decoded secret is received. Also, `cargo xtask change-channel` won't work with the `nousb` variants so you can skip that test.
## References

View File

@ -18,8 +18,8 @@ To implement responding to a GET_DESCRIPTOR Device request, extend `usb-3.rs` so
- `bDescriptorType = 1`, device descriptor type (must always be this value)
- `bDeviceClass = bDeviceSubClass = bDeviceProtocol = 0`, these are unimportant for enumeration
- `bMaxPacketSize0 = 64`, this is the most performant option (minimizes exchanges between the device and the host) and it's assumed by the `Ep0In` abstraction
- `idVendor = consts::VID`, value expected by the `usb-list` tool (\*)
- `idProduct = consts::PID`, value expected by the `usb-list` tool (\*)
- `idVendor = consts::VID`, value expected by `cargo xtask usb-list` (\*)
- `idProduct = consts::PID`, value expected by `cargo xtask usb-list` (\*)
- `bcdDevice = 0x0100`, this means version 1.0 but any value should do
- `iManufacturer = iProduct = iSerialNumber = None`, string descriptors not supported
- `bNumConfigurations = 1`, must be at least `1` so this is the minimum value

View File

@ -16,25 +16,27 @@ When put in bootloader mode the Dongle will run a bootloader program instead of
When the Dongle is in bootloader mode its red LED will oscillate in intensity. The Dongle will also appear as a USB CDC ACM device with vendor ID `0x1915` and product ID `0x521f`.
You can also use our `usb-list` tool, a minimal cross-platform version of the `lsusb` tool, to check out the status of the Dongle.
You can also use our `cargo xtask usb-list` tool, a minimal cross-platform version of the `lsusb` tool, to check out the status of the Dongle.
✅ Run `usb-list` to list all USB devices; the Dongle will be highlighted in the output, along with a note if in bootloader mode.
✅ Run `cargo xtask usb-list` to list all USB devices; the Dongle will be highlighted in the output, along with a note if in bootloader mode.
Output should look like this:
``` console
$ usb-list
$ cargo xtask usb-list
(..)
Bus 001 Device 016: ID 1915:521f <- nRF52840 Dongle (in bootloader mode)
```
🔎 [`cargo xtask`](https://github.com/matklad/cargo-xtask) lets us extend `cargo` with custom commands which are installed as you run them for the first time. We've used it to add some helper tools to our workshop materials while keeping the preparation installations as minimal as possible.
Now that the device is in bootloader mode browse to the `boards/dongle` directory. You'll find some `*.hex` files there. These are pre-compiled Rust programs that have been converted into the Intel Hex format that the `nrfutil` tool expects.
For the next section you'll need to flash the `loopback.hex` file into the Dongle. There are two ways to do this. You can make 2 long `nrfutil` invocations or you can use our `dongle-flash` tool, which will invoke `nrfutil` for you. The `dongle-flash` way is shown below:
For the next section you'll need to flash the `loopback.hex` file into the Dongle. There are two ways to do this. You can make 2 long `nrfutil` invocations or you can use our `cargo xtask dongle-flash` tool, which will invoke `nrfutil` for you. The `dongle-flash` way is shown below:
✅ Run the following command:
``` console
$ dongle-flash loopback.hex
$ cargo xtask dongle-flash boards/dongle/loopback.hex
```
Expected output:
@ -49,53 +51,53 @@ After the device has been programmed it will automatically reset and start runni
The `loopback` application will *blink* the red LED in a heartbeat fashion: two fast blinks (LED on then off) followed by two periods of silence (LED off). The application will also make the Dongle enumerate itself as a CDC ACM device.
✅ Run the `usb-list` tool to see the newly enumerated Dongle in the output:
✅ Run `cargo xtask usb-list` tool to see the newly enumerated Dongle in the output:
``` console
$ usb-list
$ cargo xtask usb-list
(..)
Bus 001 Device 020: ID 2020:0309 <- nRF52840 Dongle (loopback.hex)
```
The `loopback` app will log messages over the USB interface. To display these messages on the host we have provided a cross-platform tool: `serial-term`.
The `loopback` app will log messages over the USB interface. To display these messages on the host we have provided a cross-platform tool: `cargo xtask serial-term`.
❗ Do not use serial terminal emulators like `minicom` or `screen`. They use the USB TTY ACM interface in a slightly different manner and may result in data loss.
✅ Run the `serial-term` application. It shows you the logging output the Dongle is sending on its serial interface to your computer. This helps you monitor what's going on at the Dongle and debug connection issues. You should see the following output:
✅ Run `cargo xtask serial-term`. It shows you the logging output the Dongle is sending on its serial interface to your computer. This helps you monitor what's going on at the Dongle and debug connection issues. You should see the following output:
``` console
$ serial-term
$ cargo xtask serial-term
deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback.hex
```
This line is printed by the `loopback` app on boot. It contains the device ID of the dongle, a 64-bit unique identifier (so everyone will see a different number); the radio channel that the device will use to communicate; and the transmission power of the radio in dBm.
If you don't get any output from `serial-term` check [the USB dongle troubleshooting section][usb-issues].
If you don't get any output from `cargo xtask serial-term` check [the USB dongle troubleshooting section][usb-issues].
[usb-issues]: /troubleshoot-usb-dongle.html
## Interference
At this point you should *not* get more output from `serial-term`.
At this point you should *not* get more output from `cargo xtask serial-term`.
❗If you get "received N bytes" lines in output like this:
``` console
$ serial-term
$ cargo xtask serial-term
deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm
received 7 bytes (CRC=Ok(0x2459), LQI=0)
received 5 bytes (CRC=Ok(0xdad9), LQI=0)
received 6 bytes (CRC=Ok(0x72bb), LQI=0)
```
That means the device is observing interference traffic, likely from 2.4 GHz WiFi or Bluetooth. In this scenario you should switch the listening channel to one where you don't observe interference. Use the `tools/change-channel` tool to do this. The tool takes a single argument: the new listening channel which must be in the range 11-26.
That means the device is observing interference traffic, likely from 2.4 GHz WiFi or Bluetooth. In this scenario you should switch the listening channel to one where you don't observe interference. Use the `cargo xtask change-channel` tool to do this. The tool takes a single argument: the new listening channel which must be in the range 11-26.
``` console
$ change-channel 11
$ cargo xtask change-channel 11
requested channel change to channel 11
```
Then you should see new output from `serial-term`:
Then you should see new output from `cargo xtask serial-term`:
``` console
deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm
@ -103,4 +105,4 @@ deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm
now listening on channel 11
```
Leave the Dongle connected and the `serial-term` application running. Now we'll switch back to the Development Kit.
Leave the Dongle connected and `cargo xtask serial-term` running. Now we'll switch back to the Development Kit.

View File

@ -55,7 +55,7 @@ You can find traces for other OSes in these files (they are in the `advanced` fo
- `macos-enumeration.txt`
- `win-enumeration.txt`
✅ Double check that the enumeration works by running the [`usb-list` tool](./listing-usb-devices.md) while `usb-4.rs` is running.
✅ Double check that the enumeration works by running [`cargo xtask usb-list`](./listing-usb-devices.md) while `usb-4.rs` is running.
``` console
Bus 001 Device 013: ID 1366:1015 <- J-Link on the nRF52840 Development Kit

View File

@ -1,5 +1,18 @@
# Installation Instructions
## Workshop Materials
Clone and change into the [workshop git repository](https://github.com/ferrous-systems/embedded-trainings-2020):
```console
$ git clone https://github.com/ferrous-systems/embedded-trainings-2020.git
$ cd embedded-trainings-2020
```
The workshop repository contains all workshop materials, i.e. code snippets, custom tools and the source for this handbook.
All programming will take place in its `beginner/` and `advanced/` subfolders.
## VS Code
**Windows**: Go to [https://code.visualstudio.com](https://code.visualstudio.com) and run the installer.
@ -146,46 +159,6 @@ $ cargo install flip-link
Installed package `flip-link v0.1.2` (..)
```
### Workshop specific tools
The [workshop git repository](https://github.com/ferrous-systems/embedded-trainings-2020) contains custom crates that help you with flashing and debugging the workshops. Depending on the workshop you're attending, you need to install all or some of them.
In any case, clone and change into the repository first:
```console
$ git clone https://github.com/ferrous-systems/embedded-trainings-2020.git
$ cd embedded-trainings-2020
```
#### Beginner workshop
Change to the `tools` folder and run these commands *from different terminals so they'll run in parallel*:
```console
$ cd embedded-trainings-2020/tools
$ cargo install --path usb-list
$ # in a new terminal, for parallelization
$ cargo install --path dongle-flash
$ # in a new terminal, for parallelization
$ cargo install --path serial-term
$ # in a new terminal, for parallelization
$ cargo install --path change-channel
```
Leave the processes running in the background.
#### Advanced workshop
Change to the `tools` folder and run these commands:
```console
$ cd embedded-trainings-2020/tools
$ cargo install --path usb-list
```
Leave the process running in the background.
## Python
**Windows**: Go to [https://www.python.org/downloads/](https://www.python.org/downloads/) and run the Python *3* installer

View File

@ -1,13 +1,10 @@
# Listing USB Devices
✅ To list all USB devices, run `usb-list`.
✅ To list all USB devices, run `cargo xtask usb-list` from the `advanced` folder.
❗️ If you haven't yet installed `usb-list`; [installation instructions can be found in a previous section][install].
[install]: ./tooling-check.md#more-tools
``` console
$ usb-list
$ cargo xtask usb-list
Bus 002 Device 001: ID 1d6b:0003
Bus 001 Device 002: ID 0cf3:e300
Bus 001 Device 003: ID 0c45:6713
@ -15,11 +12,11 @@ Bus 001 Device 001: ID 1d6b:0002
Bus 001 Device 010: ID 1366:1015 <- J-Link on the nRF52840 Development Kit
```
The goal of this workshop is to get the nRF52840 SoC to show in this list. The embedded application will use the vendor ID (VID) and product ID (PID) defined in `advanced/common/consts`; the `usb-list` tool will highlight the USB device that matches that VID PID pair.
The goal of this workshop is to get the nRF52840 SoC to show in this list. The embedded application will use the vendor ID (VID) and product ID (PID) defined in `advanced/common/consts`; `cargo xtask usb-list` will highlight the USB device that matches that VID PID pair.
``` console
$ # expected output
$ usb-list
$ cargo xtask usb-list
Bus 002 Device 001: ID 1d6b:0003
Bus 001 Device 002: ID 0cf3:e300
Bus 001 Device 003: ID 0c45:6713

View File

@ -1,6 +1,6 @@
# Radio In
In this section we'll explore the `recv_timeout` method of the Radio API. As the name implies, this is used to listen for packets. The method will block the program execution until a packet is received or the specified timeout has expired. We'll continue to use the Dongle in this section; it should be running the `loopback` application; and the `serial-term` application should also be running in the background.
In this section we'll explore the `recv_timeout` method of the Radio API. As the name implies, this is used to listen for packets. The method will block the program execution until a packet is received or the specified timeout has expired. We'll continue to use the Dongle in this section; it should be running the `loopback` application; and `cargo xtask serial-term` should also be running in the background.
The `loopback` application running on the Dongle will broadcast a radio packet after receiving one over channel 20. The contents of this outgoing packet will be the contents of the received one but reversed.

View File

@ -2,10 +2,10 @@
✅ Open the `src/bin/radio-send.rs` file.
✅ First run the program `radio-send.rs` as it is. You should see new output in the output of the `serial-term` program.
✅ First run the program `radio-send.rs` as it is. You should see new output in the output of `cargo xtask serial-term`.
``` console
$ serial-term
$ cargo xtask serial-term
deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback.hex
received 5 bytes (LQI=49)
```

View File

@ -15,34 +15,3 @@ nrfutil version 6.1.0
```
✅ Now let's make sure you've installed the tools shipped with the workshop material.
### Beginner workshop
Run the commands listed here and see if they produce similar output, i.e. *don't* yield `command not found: ...`
```console
$ usb-list
Bus 020 Device 007: ID 1b1c:0a42
Bus 020 Device 006: ID 1fc9:0132
(..)
$ dongle-flash
Error: expected exactly one argument
$ serial-term
(waiting for the Dongle to be connected)
$ change-channel
Error: expected exactly one argument
```
### Advanced workshop
Run the commands listed here and see if they produce similar output, i.e. *don't* yield `command not found: ...`
```console
$ usb-list
Bus 020 Device 007: ID 1b1c:0a42
Bus 020 Device 006: ID 1fc9:0132
(..)
```

View File

@ -2,43 +2,43 @@
> NOTE: this section only applies to the Beginner workshop
If you don't get any output from `serial-term` it could just have been that first line got lost when re-enumerating the device from bootloader mode to the loopback application.
If you don't get any output from `cargo xtask serial-term` it could just have been that first line got lost when re-enumerating the device from bootloader mode to the loopback application.
Start `serial-term` in one console window.
Run `cargo xtask serial-term` in one console window. Leave this window open.
In another window, run these *two* commands:
``` console
$ change-channel 20
$ cargo xtask change-channel 20
requested channel change to channel 20
$ change-channel 20
$ cargo xtask change-channel 20
requested channel change to channel 20
```
If you get *two* lines of output in `serial-term` like this, you are good to go:
If you get *two* lines of output in `cargo xtask serial-term` like this, you are good to go:
``` console
$ serial-term
$ cargo xtask serial-term
now listening on channel 20
now listening on channel 20
```
Return to the ["Interference"] section.
🔎 `serial-term` shows you the log output that the Dongle is sending to your computer via the serial interface (not over the wireless network!). After you've ran `change-channel`, it tells you that it is now listening for network traffic on channel 20. This is helpful for debugging, but not mission-critical.
🔎 `cargo xtask serial-term` shows you the log output that the Dongle is sending to your computer via the serial interface (not over the wireless network!). After you've ran `cargo xtask change-channel`, it tells you that it is now listening for network traffic on channel 20. This is helpful for debugging, but not mission-critical.
["Interference"]: /dongle.html#interference
If you only get one line of output then your OS may be losing some serial data -- we have seen this behavior on some macOS machines. You will still be able to work through the exercises but will miss log data every now and then. Return to the ["Interference"] section.
If you don't get *any* output from `serial-term` and/or the `change-channel` command fails then the Dongle's USB functionality is not working correctly.
If you don't get *any* output from `cargo xtask serial-term` and/or the `cargo xtask change-channel` command fails then the Dongle's USB functionality is not working correctly.
In this case you should flash one of the `loopback-nousb*` programs:
Put the device in bootloader mode again. Now, run
```console
$ dongle-flash loopback-nousb21 # you can pick 11, 16, 21 or 26
$ cargo xtask dongle-flash boards/dongle/loopback-nousb21.hex # you can pick 11, 16, 21 or 26
```
❗️ The number in the `loopback-nousb*` file name is the radio channel the Dongle will listen on. This means that when you program the Development Kit to send data to the Dongle, you need to ensure they are communicating on the same channel by setting
@ -48,6 +48,6 @@ $ dongle-flash loopback-nousb21 # you can pick 11, 16, 21 or 26
radio.set_channel(Channel::_21);
```
Note that the `loopback-nousb*` programs do not send you any logs via `serial-term` for debugging but you will be able do the exercises nonetheless.
Note that the `loopback-nousb*` programs do not send you any logs via `cargo xtask serial-term` for debugging but you will be able do the exercises nonetheless.
For your debugging convenience, the Dongle will toggle the state of its green LED when it receives a packet.
When you're done, return to the ["Interference"] section.

View File

@ -1,7 +0,0 @@
[workspace]
members = [
"change-channel",
"dongle-flash",
"serial-term",
"usb-list",
]

View File

@ -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" }

View File

@ -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
}

View File

@ -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"

View File

@ -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"

View File

@ -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(())
}

View File

@ -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"

View File

@ -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(())
}

2
xtask/.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[alias]
xtask = "run --manifest-path ./Cargo.toml --"

17
xtask/Cargo.toml Normal file
View 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
View File

@ -0,0 +1 @@
pub const DONGLE_PATH: &[&str] = &["boards", "dongle"];

61
xtask/src/main.rs Normal file
View File

@ -0,0 +1,61 @@
#![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] => {
let hexpath = env::current_dir()?.join(hex);
tasks::dongle_flash(hexpath.to_str().unwrap())
}
["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())
}

View File

@ -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()))?,
@ -48,17 +77,18 @@ if the red LED was blinking and you got this message then the device wasn't corr
let ihex = if is_hex {
path.to_owned()
} else {
// TODO move to own function
// ELF -> IHEX
// 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 +178,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(())
}