diff --git a/lr-wpan-rs-tests/Cargo.toml b/lr-wpan-rs-tests/Cargo.toml index 34c94a3..82c01f7 100644 --- a/lr-wpan-rs-tests/Cargo.toml +++ b/lr-wpan-rs-tests/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -lr-wpan-rs = { path = "../lr-wpan-rs", features = ["std"] } +lr-wpan-rs = { path = "../lr-wpan-rs", features = ["std", "log-04"] } pcap-file = { version = "2.0.0" } log = { version = "0.4.22" } tokio = { version = "1.41.0", default-features = false, features = ["time", "test-util"] } diff --git a/lr-wpan-rs-tests/src/aether/radio.rs b/lr-wpan-rs-tests/src/aether/radio.rs index 4e95c88..31f650a 100644 --- a/lr-wpan-rs-tests/src/aether/radio.rs +++ b/lr-wpan-rs-tests/src/aether/radio.rs @@ -1,5 +1,6 @@ use std::sync::{Arc, Mutex, MutexGuard}; +use log::trace; use lr_wpan_rs::{ phy::{ModulationType, Phy, ReceivedMessage, SendContinuation, SendResult}, pib::{PhyPib, PhyPibWrite}, @@ -51,6 +52,8 @@ impl Phy for AetherRadio { const MODULATION: ModulationType = ModulationType::BPSK; async fn reset(&mut self) -> Result<(), Self::Error> { + trace!("Radio reset {:?}", self.node_id); + self.stop_receive().await?; let new_pib = PhyPib::unspecified_new(); self.with_node(|node| { @@ -74,8 +77,10 @@ impl Phy for AetherRadio { send_time: Option, _ranging: bool, _use_csma: bool, - _continuation: SendContinuation, + continuation: SendContinuation, ) -> Result { + trace!("Radio send {:?}", self.node_id); + let now = send_time .unwrap_or_else(|| std::time::Instant::from(tokio::time::Instant::now()).into()); tokio::time::sleep_until(now.into_std().into()).await; @@ -84,11 +89,19 @@ impl Phy for AetherRadio { let channel = self.local_pib.current_channel; self.aether().send(AirPacket::new(data, now, channel)); + match continuation { + SendContinuation::Idle => {} + SendContinuation::WaitForResponse { .. } => todo!(), + SendContinuation::ReceiveContinuous => self.start_receive().await?, + } + // TODO: Handle congestion Ok(SendResult::Success(now)) } async fn start_receive(&mut self) -> Result<(), Self::Error> { + trace!("Radio start_receive {:?}", self.node_id); + self.with_node(|node| { node.rx_enable = true; }); @@ -97,6 +110,8 @@ impl Phy for AetherRadio { } async fn stop_receive(&mut self) -> Result<(), Self::Error> { + trace!("Radio stop_receive {:?}", self.node_id); + self.with_node(|node| { node.rx_enable = false; }); @@ -134,6 +149,8 @@ impl Phy for AetherRadio { &mut self, ctx: Self::ProcessingContext, ) -> Result, Self::Error> { + trace!("Radio process {:?}", self.node_id); + Ok(Some(ctx)) } diff --git a/lr-wpan-rs-tests/tests/scan_beacons.rs b/lr-wpan-rs-tests/tests/scan.rs similarity index 75% rename from lr-wpan-rs-tests/tests/scan_beacons.rs rename to lr-wpan-rs-tests/tests/scan.rs index 93a89d3..ade129d 100644 --- a/lr-wpan-rs-tests/tests/scan_beacons.rs +++ b/lr-wpan-rs-tests/tests/scan.rs @@ -12,7 +12,11 @@ use lr_wpan_rs::{ IndicationValue, PanDescriptor, SecurityInfo, Status, }, time::Instant, - wire::{command::Command, Frame, FrameContent, PanId, ShortAddress}, + wire::{ + beacon::{BeaconOrder, SuperframeOrder}, + command::Command, + Address, Frame, FrameContent, PanId, ShortAddress, + }, ChannelPage, }; use test_log::test; @@ -23,22 +27,29 @@ async fn scan_passive() { runner.aether.start_trace("scan_passive"); - start_beacon(runner.commanders[0], 0).await; - start_beacon(runner.commanders[1], 1).await; + // Start the beacons + start_beacon(runner.commanders[0], 0, true).await; + start_beacon(runner.commanders[1], 1, false).await; + // Perform the scan, passively let (scan_confirm, notifications) = perform_scan(runner.commanders[2], ScanType::Passive, &[0, 1, 2], true).await; - let trace = runner.aether.stop_trace(); - let mut messages = runner.aether.parse_trace(trace); + // Scan needs to be successful + assert_eq!(scan_confirm.status, Status::Success); + // We should've scanned one device since we've done a passive scan + assert_eq!(scan_confirm.result_list_size, 1); + // All channels should be scanned + assert!(scan_confirm.unscanned_channels.is_empty()); + // Auto request was true, so we should've gotten zero beacon notifications assert!(notifications.is_empty()); + let trace = runner.aether.stop_trace(); + // All the messages in the aether should be beacons + let mut messages = runner.aether.parse_trace(trace); assert!(messages.all(|m| matches!(m.content, FrameContent::Beacon(_)))); - assert_eq!(scan_confirm.status, Status::Success); - assert_eq!(scan_confirm.result_list_size, 2); - assert!(scan_confirm.unscanned_channels.is_empty()); pretty_assertions::assert_eq!( scan_confirm.pan_descriptor_list().nth(0).unwrap(), &PanDescriptor { @@ -61,28 +72,7 @@ async fn scan_passive() { code_list: () } ); - pretty_assertions::assert_eq!( - scan_confirm.pan_descriptor_list().nth(1).unwrap(), - &PanDescriptor { - coord_address: lr_wpan_rs::wire::Address::Short(PanId(1), ShortAddress(1)), - channel_number: 0, - channel_page: ChannelPage::Uwb, - super_frame_spec: lr_wpan_rs::wire::beacon::SuperframeSpecification { - beacon_order: lr_wpan_rs::wire::beacon::BeaconOrder::BeaconOrder(10), - superframe_order: lr_wpan_rs::wire::beacon::SuperframeOrder::SuperframeOrder(10), - final_cap_slot: 0, - battery_life_extension: false, - pan_coordinator: true, - association_permit: false - }, - gts_permit: false, - link_quality: 255, - timestamp: Instant::from_ticks(9830400000), - security_status: None, - security_info: SecurityInfo::new_none_security(), - code_list: () - } - ); + assert_eq!(scan_confirm.pan_descriptor_list().nth(1), None); } #[test(tokio::test(unhandled_panic = "shutdown_runtime", start_paused = true))] @@ -91,17 +81,29 @@ async fn scan_active() { runner.aether.start_trace("scan_active"); - start_beacon(runner.commanders[0], 0).await; - start_beacon(runner.commanders[1], 1).await; + // Start the beacons + start_beacon(runner.commanders[0], 0, true).await; + start_beacon(runner.commanders[1], 1, false).await; - let (scan_confirm, notifications) = + // Perform the scan, actively + let (mut scan_confirm, notifications) = perform_scan(runner.commanders[2], ScanType::Active, &[0], true).await; - let trace = runner.aether.stop_trace(); - let mut messages = runner.aether.parse_trace(trace); + // Scan needs to be successful + assert_eq!(scan_confirm.status, Status::Success); + // We should've scanned two devices since we've done an active scan + assert_eq!(scan_confirm.result_list_size, 2); + // All channels should be scanned + assert!(scan_confirm.unscanned_channels.is_empty()); + // Auto request was true, so we should've gotten zero beacon notifications assert!(notifications.is_empty()); + let trace = runner.aether.stop_trace(); + + let mut messages = runner.aether.parse_trace(trace); + + // We expect a beacon request and then only beacons let first_message = messages.next(); assert!( matches!( @@ -115,11 +117,11 @@ async fn scan_active() { ); assert!(messages.all(|m| matches!(m.content, FrameContent::Beacon(_)))); - assert_eq!(scan_confirm.status, Status::Success); - assert_eq!(scan_confirm.result_list_size, 2); - assert!(scan_confirm.unscanned_channels.is_empty()); pretty_assertions::assert_eq!( - scan_confirm.pan_descriptor_list().nth(0).unwrap(), + scan_confirm + .pan_descriptor_list() + .find(|pd| pd.coord_address == Address::Short(PanId(0), ShortAddress(0))) + .unwrap(), &PanDescriptor { coord_address: lr_wpan_rs::wire::Address::Short(PanId(0), ShortAddress(0)), channel_number: 0, @@ -140,15 +142,23 @@ async fn scan_active() { code_list: () } ); + + let non_beacon_pan = scan_confirm + .pan_descriptor_list_mut() + .find(|pd| pd.coord_address == Address::Short(PanId(1), ShortAddress(1))) + .unwrap(); + // We don't want to test the timestamp since that changes (even in simulation) + non_beacon_pan.timestamp = Instant::from_seconds(0); + pretty_assertions::assert_eq!( - scan_confirm.pan_descriptor_list().nth(1).unwrap(), + non_beacon_pan, &PanDescriptor { coord_address: lr_wpan_rs::wire::Address::Short(PanId(1), ShortAddress(1)), channel_number: 0, channel_page: ChannelPage::Uwb, super_frame_spec: lr_wpan_rs::wire::beacon::SuperframeSpecification { - beacon_order: lr_wpan_rs::wire::beacon::BeaconOrder::BeaconOrder(10), - superframe_order: lr_wpan_rs::wire::beacon::SuperframeOrder::SuperframeOrder(10), + beacon_order: lr_wpan_rs::wire::beacon::BeaconOrder::OnDemand, + superframe_order: lr_wpan_rs::wire::beacon::SuperframeOrder::Inactive, final_cap_slot: 0, battery_life_extension: false, pan_coordinator: true, @@ -156,7 +166,7 @@ async fn scan_active() { }, gts_permit: false, link_quality: 255, - timestamp: Instant::from_ticks(9830400000), + timestamp: Instant::from_ticks(0), security_status: None, security_info: SecurityInfo::new_none_security(), code_list: () @@ -166,21 +176,35 @@ async fn scan_active() { #[test(tokio::test(unhandled_panic = "shutdown_runtime", start_paused = true))] async fn scan_passive_no_auto_request() { + // Goal is to scan without auto request which sends out the data as indications + // The indications should be the same as what's being sent out on the aether + let mut runner = lr_wpan_rs_tests::run::run_mac_engine_multi(3); runner.aether.start_trace("scan_passive_no_auto"); - start_beacon(runner.commanders[0], 0).await; - start_beacon(runner.commanders[1], 1).await; + // Start the beacons + start_beacon(runner.commanders[0], 0, true).await; + start_beacon(runner.commanders[1], 1, true).await; + // Do the scan, passively, without auto request let (scan_confirm, notifications) = perform_scan(runner.commanders[2], ScanType::Passive, &[0, 1, 2], false).await; - let trace = runner.aether.stop_trace(); - - let messages = runner.aether.parse_trace(trace); + // Scan must have succeeded + assert_eq!(scan_confirm.status, Status::Success); + // No list, since we should've gotten the info as indications + assert_eq!(scan_confirm.result_list_size, 0); + assert_eq!(scan_confirm.pan_descriptor_list().count(), 0); + // All channels should have been scanned + assert!(scan_confirm.unscanned_channels.is_empty()); + // Notifications must NOT be empty assert!(!notifications.is_empty()); + let trace = runner.aether.stop_trace(); + + // The notifications should follow the messages on the aether + let messages = runner.aether.parse_trace(trace); for (message, notification) in messages.zip(notifications) { match message.content { FrameContent::Beacon(beacon) => { @@ -199,13 +223,11 @@ async fn scan_passive_no_auto_request() { _ => unimplemented!("Not seen in test"), } } - - assert_eq!(scan_confirm.status, Status::Success); - assert_eq!(scan_confirm.result_list_size, 0); - assert!(scan_confirm.unscanned_channels.is_empty()); } -async fn start_beacon(commander: &MacCommander, id: u16) { +// TODO: A test with auto request enabled and more PANs being scanned than can fit in the allocation + +async fn start_beacon(commander: &MacCommander, id: u16, emit_beacons: bool) { let reset_response = commander .request(ResetRequest { set_default_pib: true, @@ -227,8 +249,16 @@ async fn start_beacon(commander: &MacCommander, id: u16) { channel_number: 0, channel_page: ChannelPage::Uwb, start_time: 0, - beacon_order: lr_wpan_rs::wire::beacon::BeaconOrder::BeaconOrder(10), - superframe_order: lr_wpan_rs::wire::beacon::SuperframeOrder::SuperframeOrder(10), + beacon_order: if emit_beacons { + BeaconOrder::BeaconOrder(10) + } else { + BeaconOrder::OnDemand + }, + superframe_order: if emit_beacons { + SuperframeOrder::SuperframeOrder(10) + } else { + SuperframeOrder::Inactive + }, pan_coordinator: true, battery_life_extension: false, coord_realignment: false, @@ -257,7 +287,9 @@ async fn perform_scan( pib_attribute: PibValue::MAC_AUTO_REQUEST, pib_attribute_value: PibValue::MacAutoRequest(auto_request), }) - .await; + .await + .status + .unwrap(); let mut wait = core::pin::pin!(commander.wait_for_indication().fuse()); diff --git a/lr-wpan-rs/Cargo.toml b/lr-wpan-rs/Cargo.toml index c81aa6d..905a687 100644 --- a/lr-wpan-rs/Cargo.toml +++ b/lr-wpan-rs/Cargo.toml @@ -26,6 +26,7 @@ cipher = { version = "0.3.0", default-features = false } defmt = { version = "0.3.8", optional = true } log = { version = "0.4.22", optional = true } rand_core = "0.9.0" +derive_more = { version = "2.0.1", default-features = false, features = ["display"] } [dev-dependencies] rand = "0.9.0" diff --git a/lr-wpan-rs/src/mac/commander.rs b/lr-wpan-rs/src/mac/commander.rs index 0c444be..23faae2 100644 --- a/lr-wpan-rs/src/mac/commander.rs +++ b/lr-wpan-rs/src/mac/commander.rs @@ -24,6 +24,7 @@ impl MacCommander { /// Make a request to the MAC layer. The typed confirm response is returned. /// This API is cancel-safe, though the request may not have been sent at the point of cancellation. + #[must_use] pub async fn request(&self, request: R) -> R::Confirm { self.request_confirm_channel .request(request.into()) @@ -33,6 +34,7 @@ impl MacCommander { /// Make a request to the MAC layer. The typed confirm response is returned. /// This API is cancel-safe, though the request may not have been sent at the point of cancellation. + #[must_use] pub async fn request_with_allocation<'a, R: DynamicRequest>( &self, mut request: R, diff --git a/lr-wpan-rs/src/mac/mlme_scan.rs b/lr-wpan-rs/src/mac/mlme_scan.rs index ca9e96d..c2bb841 100644 --- a/lr-wpan-rs/src/mac/mlme_scan.rs +++ b/lr-wpan-rs/src/mac/mlme_scan.rs @@ -227,7 +227,7 @@ impl ScanProcess<'_> { .remove(self.skipped_channels); } ScanAction::Finish => { - info!("Scan has been finished!") + debug!("Scan has been finished!") } } } diff --git a/lr-wpan-rs/src/mac/mod.rs b/lr-wpan-rs/src/mac/mod.rs index c5e4340..b33e741 100644 --- a/lr-wpan-rs/src/mac/mod.rs +++ b/lr-wpan-rs/src/mac/mod.rs @@ -5,6 +5,7 @@ use crate::{ pib::MacPib, sap::{scan::ScanType, RequestValue, Status}, time::{DelayNsExt, Duration, Instant}, + wire::command::Command, }; mod callback; @@ -164,9 +165,17 @@ async fn wait_for_radio_event( let symbol_duration = phy.symbol_duration(); let current_time_symbols = current_time / symbol_duration; - // TODO: Figure out whether we should put the radio in RX + // TODO: Figure out when exactly we should put the radio in RX // - For example when PAN coordinator, but with beaconorder 15 // - For example when PIB says so + if mac_state.is_pan_coordinator && mac_pib.beacon_order.is_on_demand() + || mac_pib.rx_on_when_idle + { + if let Err(e) = phy.start_receive().await { + error!("Could not start receiving: {}", e); + return RadioEvent::Error; + } + } let own_superframe_start = wait_for_own_superframe_start( mac_pib, @@ -212,43 +221,55 @@ async fn wait_for_radio_event( } async fn handle_radio_event( - event: RadioEvent

, + mut event: RadioEvent

, phy: &mut P, mac_pib: &mut MacPib, mac_state: &mut MacState<'_>, mac_handler: &MacHandler<'_>, ) { - match event { - RadioEvent::Error => todo!(), - RadioEvent::OwnSuperframeStart { start_time } => { - own_superframe_start(mac_state, mac_pib, phy, start_time).await - } - RadioEvent::OwnSuperframeStartMissed { start_time } => { - // Reset so hopefully the next time works out - mac_pib.beacon_tx_time = start_time / phy.symbol_duration(); - } - RadioEvent::OwnSuperframeEnd => { - mac_state.own_superframe_active = false; - - if !mac_pib.rx_on_when_idle { - if let Err(e) = phy.stop_receive().await { - error!( - "Could not stop the radio receiving at the end of the superframe: {}", - e - ); + loop { + match event { + RadioEvent::Error => todo!(), + RadioEvent::BeaconRequested => send_beacon(mac_state, mac_pib, phy, None, true).await, + RadioEvent::OwnSuperframeStart { start_time } => { + send_beacon(mac_state, mac_pib, phy, Some(start_time), false).await + } + RadioEvent::OwnSuperframeStartMissed { start_time } => { + // Reset so hopefully the next time works out + mac_pib.beacon_tx_time = start_time / phy.symbol_duration(); + } + RadioEvent::OwnSuperframeEnd => { + mac_state.own_superframe_active = false; + + if !mac_pib.rx_on_when_idle { + if let Err(e) = phy.stop_receive().await { + error!( + "Could not stop the radio receiving at the end of the superframe: {}", + e + ); + } } } - } - RadioEvent::PhyWaitDone { context } => match phy.process(context).await { - Ok(Some(message)) => process_message(message, mac_state, mac_pib, mac_handler).await, - Ok(None) => {} - Err(e) => { - error!("Phy process error: {}", e); + RadioEvent::PhyWaitDone { context } => match phy.process(context).await { + Ok(Some(message)) => { + if let Some(next_event) = + process_message::

(message, mac_state, mac_pib, mac_handler).await + { + event = next_event; + continue; + } + } + Ok(None) => {} + Err(e) => { + error!("Phy process error: {}", e); + } + }, + RadioEvent::ScanAction(scan_action) => { + perform_scan_action(scan_action, phy, mac_state, mac_pib).await } - }, - RadioEvent::ScanAction(scan_action) => { - perform_scan_action(scan_action, phy, mac_state, mac_pib).await } + + break; } } @@ -287,6 +308,10 @@ async fn perform_scan_action( } let mut scan_type = scan_type; + debug!( + "Scanning channel '{}' of page '{:?}' with type '{:?}'", + channel, page, scan_type + ); loop { match scan_type { ScanType::Ed => { @@ -317,6 +342,7 @@ async fn perform_scan_action( footer: [0, 0], }); + trace!("Sending beacon request"); match phy .send( &data, @@ -390,16 +416,30 @@ async fn perform_scan_action( } } -async fn own_superframe_start( +async fn send_beacon( mac_state: &mut MacState<'_>, mac_pib: &mut MacPib, phy: &mut impl Phy, - start_time: Instant, + send_time: Option, + use_beacon_csma: bool, ) { use crate::wire; let has_broadcast_scheduled = mac_state.message_scheduler.has_broadcast_scheduled(); - mac_state.own_superframe_active = true; + mac_state.own_superframe_active = !mac_pib.superframe_order.is_inactive(); + + if mac_state.own_superframe_active { + trace!("Starting a new superframe"); + } else { + trace!("Sending a beacon") + } + + let beacon_send_continuation = if mac_state.own_superframe_active || mac_pib.rx_on_when_idle { + SendContinuation::ReceiveContinuous + } else { + SendContinuation::Idle + }; + let beacon_frame = wire::Frame { header: wire::Header { frame_type: wire::FrameType::Beacon, @@ -439,31 +479,40 @@ async fn own_superframe_start( payload: &mac_pib.beacon_payload[..mac_pib.beacon_payload_length], footer: Default::default(), }; - if let Err(e) = phy + + let send_time = match phy .send( &mac_state.serialize_frame(beacon_frame), - Some(start_time), + send_time, mac_pib.ranging_supported, - false, + use_beacon_csma, if !has_broadcast_scheduled { - SendContinuation::ReceiveContinuous // The superframe of the beacon continues + beacon_send_continuation } else { SendContinuation::Idle }, ) .await { - error!("Could not send beacon: {}", e); - // Let's just act as though we did send it - } + Ok(SendResult::Success(send_time)) => send_time, + Ok(SendResult::ChannelAccessFailure) => { + warn!("Could not send beacon due to channel access failure"); + return; + } + Err(e) => { + error!("Could not send beacon: {}", e); + return; + } + }; + if let Some(broadcast) = mac_state.message_scheduler.take_scheduled_broadcast() { match phy .send( &broadcast.data, - Some(start_time), + Some(send_time), mac_pib.ranging_supported, false, - SendContinuation::ReceiveContinuous, // The superframe of the beacon continues + beacon_send_continuation, ) .await { @@ -487,11 +536,13 @@ async fn own_superframe_start( } } } - mac_pib.beacon_tx_time = start_time / phy.symbol_duration(); + + mac_pib.beacon_tx_time = send_time / phy.symbol_duration(); } enum RadioEvent { Error, + BeaconRequested, OwnSuperframeStart { start_time: Instant }, OwnSuperframeStartMissed { start_time: Instant }, OwnSuperframeEnd, @@ -603,35 +654,56 @@ async fn wait_for_channel_scan_action( } } -async fn process_message( +async fn process_message( mut message: ReceivedMessage, mac_state: &mut MacState<'_>, mac_pib: &MacPib, mac_handler: &MacHandler<'_>, -) { - info!("Received a message"); - +) -> Option> { let Some(frame) = mac_state.deserialize_frame(&mut message.data) else { - return; + trace!("Received a frame that could not be deserialized"); + return None; }; + trace!("Received a frame: {:?}", frame); + // Now decide what to do with the frame... // TODO: Filtering as in 5.1.6.2 - if let Some(scan_process) = mac_state.current_scan_process.as_mut() { - if let FrameContent::Beacon(_) = frame.content { - scan_process - .register_received_beacon( - message.timestamp, - message.lqi, - message.channel, - message.page, - frame, - mac_pib, - mac_handler, - ) - .await; + if mac_state.current_scan_process.is_some() { + // During a scan, all non-beacon frames are rejected + if !matches!(frame.content, FrameContent::Beacon(_)) { + trace!("Ignoring a beacon"); + return None; + } + } + + if matches!(frame.content, FrameContent::Command(Command::BeaconRequest)) { + if mac_state.is_pan_coordinator && mac_pib.beacon_order.is_on_demand() { + debug!("Got a beacon request to respond to"); + return Some(RadioEvent::BeaconRequested); + } else { + trace!("Ignoring a beacon request"); + return None; } } + + if let Some(scan_process) = mac_state.current_scan_process.as_mut() { + debug!("Received a beacon for the scan"); + + scan_process + .register_received_beacon( + message.timestamp, + message.lqi, + message.channel, + message.page, + frame, + mac_pib, + mac_handler, + ) + .await; + } + + None } diff --git a/lr-wpan-rs/src/phy.rs b/lr-wpan-rs/src/phy.rs index 99c5521..273d5d8 100644 --- a/lr-wpan-rs/src/phy.rs +++ b/lr-wpan-rs/src/phy.rs @@ -52,6 +52,9 @@ pub trait Phy { /// When PIB attributes are updated, the receiver must reflect them immediately, /// even if that disrupts the operation for a little bit. /// + /// If this function is called when the radio is already receiving, then nothing should happen and the + /// radio should continue receiving. + /// /// A received message is returned in the [Self::process] function. async fn start_receive(&mut self) -> Result<(), Self::Error>; @@ -90,6 +93,7 @@ pub enum SendResult { ChannelAccessFailure, } +#[derive(Clone, Copy, Debug)] pub enum SendContinuation { /// Go back to idle Idle, diff --git a/lr-wpan-rs/src/sap/associate.rs b/lr-wpan-rs/src/sap/associate.rs index 46b3ef4..c77b0ea 100644 --- a/lr-wpan-rs/src/sap/associate.rs +++ b/lr-wpan-rs/src/sap/associate.rs @@ -2,9 +2,12 @@ use super::{ ConfirmValue, DynamicRequest, Indication, IndicationValue, Request, RequestValue, ResponseValue, SecurityInfo, Status, }; -use crate::wire::{ - command::{AssociationStatus, CapabilityInformation}, - Address, ExtendedAddress, ShortAddress, +use crate::{ + wire::{ + command::{AssociationStatus, CapabilityInformation}, + Address, ExtendedAddress, ShortAddress, + }, + ChannelPage, }; /// The MLME-ASSOCIATE.request primitive is used by a device to request an association with a coordinator. @@ -21,7 +24,7 @@ pub struct AssociateRequest { /// The channel number on which to attempt association. pub channel_number: u8, /// The channel page on which to attempt association. - pub channel_page: u8, + pub channel_page: ChannelPage, /// - The coordinator addressing mode for this primitive and subsequent MPDU. /// - The identifier of the PAN with which to associate. /// - The address of the coordinator with which to associate. diff --git a/lr-wpan-rs/src/sap/mod.rs b/lr-wpan-rs/src/sap/mod.rs index 226e61a..aa73757 100644 --- a/lr-wpan-rs/src/sap/mod.rs +++ b/lr-wpan-rs/src/sap/mod.rs @@ -5,6 +5,7 @@ use beacon_notify::BeaconNotifyIndication; use calibrate::{CalibrateConfirm, CalibrateRequest}; use comm_status::CommStatusIndication; use data::{DataConfirm, DataIndication, DataRequest}; +use derive_more::Display; use disassociate::{DisassociateConfirm, DisassociateIndication, DisassociateRequest}; use dps::{DpsConfirm, DpsIndication, DpsRequest}; use get::{GetConfirm, GetRequest}; @@ -54,7 +55,8 @@ pub mod sounding; pub mod start; pub mod sync; -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub enum Status { #[default] Success, @@ -95,6 +97,15 @@ pub enum Status { ReadOnly, } +impl Status { + pub fn unwrap(&self) { + match self { + Status::Success => {} + s => panic!("Called `Status::unwrap()` on a `{}` value", s), + } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct SecurityInfo { pub security_level: SecurityLevel, @@ -447,6 +458,7 @@ pub trait Indication: From + Into { type Response: From + Into; } +#[derive(Debug)] pub enum IndicationValue { Associate(AssociateIndication), Disassociate(DisassociateIndication), diff --git a/lr-wpan-rs/src/sap/scan.rs b/lr-wpan-rs/src/sap/scan.rs index 92a70d6..cc43a22 100644 --- a/lr-wpan-rs/src/sap/scan.rs +++ b/lr-wpan-rs/src/sap/scan.rs @@ -129,6 +129,19 @@ impl ScanConfirm { .iter() .filter_map(|pdesc| pdesc.as_ref()) } + + /// The list of PAN descriptors, one for + /// each beacon found during an active or + /// passive scan if macAutoRequest is set + /// to TRUE. This parameter is null for + /// ED and orphan scans or when macAutoRequest is set to FALSE during an + /// active or passive scan. + pub fn pan_descriptor_list_mut(&mut self) -> impl Iterator + '_ { + self.pan_descriptor_list_allocation + .as_slice_mut() + .iter_mut() + .filter_map(|pdesc| pdesc.as_mut()) + } } impl From for ScanConfirm { @@ -141,6 +154,7 @@ impl From for ScanConfirm { } #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub enum ScanType { Ed, #[default] diff --git a/lr-wpan-rs/src/wire/beacon.rs b/lr-wpan-rs/src/wire/beacon.rs index c6b68e4..56e99aa 100644 --- a/lr-wpan-rs/src/wire/beacon.rs +++ b/lr-wpan-rs/src/wire/beacon.rs @@ -20,6 +20,16 @@ pub enum BeaconOrder { OnDemand, } +impl BeaconOrder { + /// Returns `true` if the beacon order is [`OnDemand`]. + /// + /// [`OnDemand`]: BeaconOrder::OnDemand + #[must_use] + pub fn is_on_demand(&self) -> bool { + matches!(self, Self::OnDemand) + } +} + impl From for BeaconOrder { /// Convert u8 to beacon order fn from(value: u8) -> Self { @@ -52,6 +62,16 @@ pub enum SuperframeOrder { Inactive, } +impl SuperframeOrder { + /// Returns `true` if the superframe order is [`Inactive`]. + /// + /// [`Inactive`]: SuperframeOrder::Inactive + #[must_use] + pub fn is_inactive(&self) -> bool { + matches!(self, Self::Inactive) + } +} + impl From for SuperframeOrder { /// Convert u8 to superframe order fn from(value: u8) -> Self {