diff --git a/beginner/README.md b/beginner/README.md index 3374b7d..b2006c8 100644 --- a/beginner/README.md +++ b/beginner/README.md @@ -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. diff --git a/beginner/apps/src/bin/radio-puzzle-1.rs b/beginner/apps/src/bin/radio-puzzle-1.rs index 7713554..8a32b6d 100644 --- a/beginner/apps/src/bin/radio-puzzle-1.rs +++ b/beginner/apps/src/bin/radio-puzzle-1.rs @@ -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 } diff --git a/beginner/apps/src/bin/radio-puzzle-3.rs b/beginner/apps/src/bin/radio-puzzle-3.rs index fd7e6ad..4edf6c0 100644 --- a/beginner/apps/src/bin/radio-puzzle-3.rs +++ b/beginner/apps/src/bin/radio-puzzle-3.rs @@ -19,7 +19,7 @@ fn main() -> ! { radio.set_channel(Channel::_25); // TODO increase capacity - let mut dict = LinearMap::::new(); + let dict = LinearMap::::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"); diff --git a/beginner/apps/src/bin/radio-puzzle-6.rs b/beginner/apps/src/bin/radio-puzzle-6.rs index 2d01721..8061a80 100644 --- a/beginner/apps/src/bin/radio-puzzle-6.rs +++ b/beginner/apps/src/bin/radio-puzzle-6.rs @@ -19,7 +19,7 @@ fn main() -> ! { /* # Build a dictionary */ // TODO increase capacity - let mut dict = LinearMap::::new(); + let dict = LinearMap::::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"); diff --git a/beginner/apps/src/bin/radio-puzzle-7.rs b/beginner/apps/src/bin/radio-puzzle-7.rs index a742fb0..c71b9f7 100644 --- a/beginner/apps/src/bin/radio-puzzle-7.rs +++ b/beginner/apps/src/bin/radio-puzzle-7.rs @@ -19,7 +19,7 @@ fn main() -> ! { /* # Build a dictionary */ // TODO increase capacity - let mut dict = LinearMap::::new(); + let dict = LinearMap::::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"); diff --git a/beginner/apps/src/bin/radio-puzzle.rs b/beginner/apps/src/bin/radio-puzzle.rs index f21116c..a5baceb 100644 --- a/beginner/apps/src/bin/radio-puzzle.rs +++ b/beginner/apps/src/bin/radio-puzzle.rs @@ -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() } diff --git a/beginner/apps/src/bin/radio-recv.rs b/beginner/apps/src/bin/radio-recv.rs index e9eb4c9..db05634 100644 --- a/beginner/apps/src/bin/radio-recv.rs +++ b/beginner/apps/src/bin/radio-recv.rs @@ -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()