- use recv_timeout everywhere

- add notes about literals
- add note about delay between send & recv
This commit is contained in:
Jorge Aparicio 2020-06-22 19:11:42 +02:00
parent affd4fa9c5
commit ea4f4e2cfc
7 changed files with 55 additions and 29 deletions

View file

@ -323,6 +323,8 @@ received 5 bytes (LQI=49)
The program broadcasts a radio packet that contains the 5-byte string `Hello` over channel 20 (which has a center frequency of 2450 MHz). The `loopback` program running on the Dongle is listening to all packets sent over channel 20; every time it receives a new packet it reports its length and the Link Quality Indicator (LQI) metric of the transmission over the USB/serial interface. As the name implies the LQI metric indicates how good the connection between the sender and the device is.
*Important note* about `b"Hello"` vs `"Hello"`. The first one is a *byte* string literal value that has type `&[u8; N]`; the second one is a *string* literal value that has type `&str` (`str` is Rust string type). The latter type, `str`, must always be valid UTF-8 data. The former type, `&[u8; N]`, does not place any constraint on its contents. However, the byte string literal syntax (`b".."`) will only accept *non-escaped* characters in the ASCII range (`0x00`..=`0x7F`) so it can *not* contain CJK (Chinese Japanese Korean) characters or emoji for example. String literals (`".."`) can contain any valid UTF-8 content so they can contain CJK characters, emoji, Greek letters, Cyrillic script, etc.
Now run the `radio-send` program several times with different variations:
- change the distance between the Dongle and the DK -- move the DK closer to or further away from the Dongle.
@ -339,23 +341,25 @@ The radio interface we are using follows the IEEE 802.15.4 specification but it'
## Radio in
In this section we'll explore the `recv` 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. 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 the `serial-term` application 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.
Open the `src/bin/radio-recv.rs` file and click the "Run" button.
The Dongle will response as soon as it receives a packet. If you insert a delay between the `send` operation and the `recv` operation in the `radio-recv` program this will result in the DK not seeing the Dongle's response.
*Important note* the Dongle expects the packet to contain only ASCII characters and will not respond to packets that contain non-ASCII data. If you only send packets that contain byte string literals *with no escaped characters* (e.g. `b"hello"`) then this requirement will be satisfied. At the same time the Dongle will always respond with ASCII data so calling `str::from_utf8` on the response should never fail, unless the packet contents got corrupted in the transmission but the CRC should detect this scenario.
In a fully IEEE 802.15.4 compliant implementation one can mark a packet as "requires acknowledgment". The recipient must respond to these packets with an acknowledgment packet; if the sender doesn't receive the acknowledgment packet it will re-transmit the packet. This feature is part of the MAC layer and not implemented in the `Radio` API we are using so packet loss is possible even when the radios are close enough to communicate.
The Dongle will respond as soon as it receives a packet. If you insert a delay between the `send` operation and the `recv` operation in the `radio-recv` program this will result in the DK not seeing the Dongle's response. So try this: add a `timer.delay(x)` call before the `recv_timeout` call; try different values of `x` and observe what happens.
Having log statements between `send` and `recv_timeout` can also cause packets to be missed so try to keep those two calls as close to each other as possible and with as little code in between as possible.
> NOTE In a fully IEEE 802.15.4 compliant implementation one can mark a packet as "requires acknowledgment". The recipient must respond to these packets with an acknowledgment packet; if the sender doesn't receive the acknowledgment packet it will re-transmit the packet. This feature is part of the MAC layer and improves packet delivery rate but it is not implemented in the `Radio` API we are using so packet loss is possible even when the radios are close enough to communicate.
## Radio puzzle
> TODO(japaric) before this section maybe cover collision avoidance
For this section you'll need to flash the `puzzle.hex` program on the Dongle. Follow the instructions from the "nRF52840 Dongle" section but flash the `puzzle.hex` program instead of the `loopback.hex` one -- don't forget to put the Dongle in bootloader mode before invoking `dongle-flash`.
Like in the previous sections the Dongle will listen for radio packets over channel 20 while also logging messages over a USB/serial interface.
Like in the previous sections the Dongle will listen for radio packets -- this time over channel 25 -- while also logging messages over a USB/serial interface.
Open the `beginner/apps` folder in VS Code; then open the `src/bin/radio-puzzle.rs` file.
@ -371,7 +375,7 @@ The Dongle will respond differently depending on the length of the incoming pack
The Dongle will always respond with packets that are valid UTF-8.
Our suggestion is to use a dictionary / map. `std::collections::HashMap` is not available in `no_std` code (without linking to a global allocator) but you can use one of the maps in the [`heapless`] crate. To make this crate available in your application get the latest version from [crates.io] and add it to the `beginner/apps/Cargo.toml` file, for example:
Our suggestion is to use a dictionary / map. `std::collections::HashMap` is not available in `no_std` code (without linking to a global allocator) but you can use one of the maps in the [`heapless`] crate. This crate is already declared as a dependency in the Cargo.toml (shown below) so you can directly import it into the application code using a `use` statement.
[`heapless`]: https://docs.rs/heapless
[crates.io]: https://crates.io/crates/heapless
@ -379,11 +383,15 @@ Our suggestion is to use a dictionary / map. `std::collections::HashMap` is not
``` toml
# Cargo.toml
[dependencies]
heapless = "0.5.0"
heapless = "0.5.5"
```
If you haven't use a stack-allocated collection before note that you'll need to specify the capacity of the collection as a type parameter using one of the "type-level values" in the `heapless::consts` module. The crate level documentation of the `heapless` crate has some examples.
Something you will likely run into while solving this exercise are *character* literals (`'c'`) and *byte* literals (`b'c'`). The former has type [`char`] and represent a single Unicode "scalar value". The latter has type `u8` (1-byte integer) and it's mainly a convenience for getting the value of ASCII characters, for instance `b'A'` is the same as the `65u8` literal.
[`char`]: https://doc.rust-lang.org/std/primitive.char.html
*IMPORTANT* you do not need to use the `str` or `char` API to solve this problem, other than for printing purposes. Work directly with slices of bytes (`[u8]`) and bytes (`u8`); and only convert those to `str` or `char` when you are about to print them.
P.S. The plaintext string is *not* stored in `puzzle.hex` so running `strings` on it will not give you the answer.

View file

@ -49,7 +49,7 @@ fn main() -> ! {
// TODO next do the whole ASCII range [0, 127]
// start small: just 'A' and 'B' at first
// OR for source in b'A'..=b'B' (NOTE: inclusive range)
for source in 65..67 {
for _source in 65..67 {
// TODO similar procedure as above
}

View file

@ -19,7 +19,7 @@ fn main() -> ! {
radio.set_channel(Channel::_25);
// TODO increase capacity
let mut dict = LinearMap::<u8, u8, consts::U2>::new();
let dict = LinearMap::<u8, u8, consts::U2>::new();
let mut packet = Packet::new();
// TODO do the whole ASCII range [0, 127]
@ -31,7 +31,7 @@ fn main() -> ! {
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
// response should be one byte large
if packet.len() == 1 {
let destination = packet[0];
let _destination = packet[0];
// TODO insert the key-value pair
// dict.insert(/* ? */, /* ? */).expect("dictionary full");

View file

@ -19,7 +19,7 @@ fn main() -> ! {
/* # Build a dictionary */
// TODO increase capacity
let mut dict = LinearMap::<u8, u8, consts::U2>::new();
let dict = LinearMap::<u8, u8, consts::U2>::new();
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
@ -34,7 +34,7 @@ fn main() -> ! {
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
// response should be one byte large
if packet.len() == 1 {
let destination = packet[0];
let _destination = packet[0];
// TODO insert the key-value pair
// dict.insert(/* ? */, /* ? */).expect("dictionary full");

View file

@ -19,7 +19,7 @@ fn main() -> ! {
/* # Build a dictionary */
// TODO increase capacity
let mut dict = LinearMap::<u8, u8, consts::U2>::new();
let dict = LinearMap::<u8, u8, consts::U2>::new();
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
@ -34,7 +34,7 @@ fn main() -> ! {
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
// response should be one byte large
if packet.len() == 1 {
let destination = packet[0];
let _destination = packet[0];
// TODO insert the key-value pair
// dict.insert(/* ? */, /* ? */).expect("dictionary full");

View file

@ -8,10 +8,13 @@ use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut timer = board.timer;
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
@ -19,18 +22,24 @@ fn main() -> ! {
let mut packet = Packet::new();
// try these
let msg = "";
// let msg = "A";
// let msg = "Hello?";
let msg = b"";
// let msg = b"A";
// let msg = b"Hello?";
packet.copy_from_slice(msg);
log::info!(
"sending: {}",
str::from_utf8(msg).expect("msg was not valid UTF-8 data")
);
packet.copy_from_slice(msg.as_bytes());
radio.send(&packet);
log::info!("sent: {:?}", msg);
// listen for a response packet and ensure it is not corrupted
if radio.recv(&mut packet).is_ok() {
// convert the packet contents to str or print error message on failure
let response = str::from_utf8(&*packet).expect("could not convert response to str");
log::info!("received: {}", response);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
log::info!(
"received: {}",
str::from_utf8(&packet).expect("response was not valid UTF-8 data")
);
} else {
log::error!("no response or response packet was corrupted");
}
dk::exit()
}

View file

@ -8,6 +8,8 @@ use cortex_m_rt::entry;
use dk::ieee802154::{Error, Packet};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
// initializes the peripherals
@ -18,10 +20,17 @@ fn main() -> ! {
let mut packet = Packet::new();
let msg = b"olleh";
packet.copy_from_slice(msg);
log::info!(
"sending: {}",
str::from_utf8(msg).expect("message is not valid UTF-8")
);
radio.send(&packet);
log::info!("sent: {:?}", msg);
let timeout = 1_000;
let res = radio.recv_timeout(&mut packet, &mut timer, timeout);
// TODO try uncommenting this line
// timer.delay(1_000);
let res = radio.recv_timeout(&mut packet, &mut timer, TEN_MS);
match res {
Ok(crc) => {
@ -32,7 +41,7 @@ fn main() -> ! {
);
}
Err(Error::Crc(crc)) => log::error!("invalid CRC: {:06x}", crc),
Err(Error::Timeout) => log::error!("no response within {} ms", timeout),
Err(Error::Timeout) => log::error!("no response within {} ms", TEN_MS / 1_000),
}
dk::exit()