|
| 1 | +//! Host example driving a remote OpenThread RCP over serial that attaches as a |
| 2 | +//! child and then **forces a router upgrade** via [`OpenThread::become_router`], |
| 3 | +//! instead of waiting for OpenThread's jittered automatic upgrade. |
| 4 | +//! |
| 5 | +//! Its purpose is to deterministically exercise the child-to-router role |
| 6 | +//! transition — which is when OpenThread calls `otPlatRadioSetAlternateShortAddress` |
| 7 | +//! with the node's old (child) RLOC16 (the alternate short address), then clears |
| 8 | +//! it ~8 s later. Run with `RUST_LOG=info` and watch for the |
| 9 | +//! `Plat radio set alternate short address callback` line. |
| 10 | +//! |
| 11 | +//! Needs the `ftd` feature (only a Full Thread Device can become a router): |
| 12 | +//! `cargo run --features ftd --bin become_router` |
| 13 | +//! |
| 14 | +//! Set the serial device with `RCP_SERIAL` (default `/dev/ttyACM0`), the baud |
| 15 | +//! rate with `RCP_BAUD` (default 115200) and, optionally, `THREAD_DATASET`. |
| 16 | +
|
| 17 | +use embassy_executor::{Executor, Spawner}; |
| 18 | + |
| 19 | +use log::{info, warn}; |
| 20 | + |
| 21 | +use openthread::spinel::{ |
| 22 | + SerialPort, SpinelRadio, SpinelRadioResources, UartSpinelTransport, UartTransportResources, |
| 23 | +}; |
| 24 | +use openthread::{DeviceRole, OpenThread, OtResources, SimpleRamSettings}; |
| 25 | + |
| 26 | +use rand::rngs::StdRng; |
| 27 | +use rand::{RngCore, SeedableRng}; |
| 28 | + |
| 29 | +use static_cell::{ConstStaticCell, StaticCell}; |
| 30 | + |
| 31 | +// Linked for its `utoa`/`strtoul` C symbols, which OpenThread's C references. |
| 32 | +use tinyrlibc as _; |
| 33 | + |
| 34 | +const DEFAULT_SERIAL: &str = "/dev/ttyACM0"; |
| 35 | +const DEFAULT_BAUD: u32 = 115_200; |
| 36 | + |
| 37 | +const THREAD_DATASET: &str = match option_env!("THREAD_DATASET") { |
| 38 | + Some(dataset) => dataset, |
| 39 | + None => "000300001901020fd80208b566147d38e384200e080000639c5d67a3bd0510c490f58d4be0d5eaeb0f09b395d1ae17030d4e4553542d50414e2d304644380708fd7d4f8232cb00000410a7e08419ae47c177fb91bcfcec789aa50c0402a0f77835060004001fffe0", |
| 40 | +}; |
| 41 | + |
| 42 | +static EXECUTOR: StaticCell<Executor> = StaticCell::new(); |
| 43 | + |
| 44 | +fn main() { |
| 45 | + env_logger::builder() |
| 46 | + .filter_level(log::LevelFilter::Info) |
| 47 | + .parse_default_env() |
| 48 | + .init(); |
| 49 | + |
| 50 | + let executor = EXECUTOR.init(Executor::new()); |
| 51 | + executor.run(|spawner| spawner.spawn(main_task(spawner).unwrap())); |
| 52 | +} |
| 53 | + |
| 54 | +#[embassy_executor::task] |
| 55 | +async fn main_task(spawner: Spawner) { |
| 56 | + let serial_path = std::env::var("RCP_SERIAL").unwrap_or_else(|_| DEFAULT_SERIAL.into()); |
| 57 | + let baud = std::env::var("RCP_BAUD") |
| 58 | + .ok() |
| 59 | + .and_then(|v| v.parse().ok()) |
| 60 | + .unwrap_or(DEFAULT_BAUD); |
| 61 | + |
| 62 | + info!("Starting; opening RCP serial {serial_path} @ {baud} baud"); |
| 63 | + |
| 64 | + static RNG: StaticCell<StdRng> = StaticCell::new(); |
| 65 | + let rng = RNG.init(StdRng::from_os_rng()); |
| 66 | + |
| 67 | + let mut ieee_eui64 = [0u8; 8]; |
| 68 | + rng.fill_bytes(&mut ieee_eui64); |
| 69 | + |
| 70 | + static OT_RESOURCES: StaticCell<OtResources> = StaticCell::new(); |
| 71 | + static OT_SETTINGS_BUF: StaticCell<[u8; 1024]> = StaticCell::new(); |
| 72 | + static OT_SETTINGS: StaticCell<SimpleRamSettings> = StaticCell::new(); |
| 73 | + |
| 74 | + let ot_resources = OT_RESOURCES.init(OtResources::new()); |
| 75 | + let ot_settings_buf = OT_SETTINGS_BUF.init([0; 1024]); |
| 76 | + let ot_settings = OT_SETTINGS.init(SimpleRamSettings::new(ot_settings_buf)); |
| 77 | + |
| 78 | + let ot = OpenThread::new(ieee_eui64, rng, ot_settings, ot_resources).unwrap(); |
| 79 | + |
| 80 | + static RADIO_RESOURCES: ConstStaticCell<SpinelRadioResources> = |
| 81 | + ConstStaticCell::new(SpinelRadioResources::new()); |
| 82 | + static UART_RESOURCES: ConstStaticCell<UartTransportResources> = |
| 83 | + ConstStaticCell::new(UartTransportResources::new()); |
| 84 | + |
| 85 | + let serial = SerialPort::open(&serial_path, baud).expect("open RCP serial"); |
| 86 | + let radio = SpinelRadio::new( |
| 87 | + UartSpinelTransport::new(serial, UART_RESOURCES.take()), |
| 88 | + RADIO_RESOURCES.take(), |
| 89 | + ); |
| 90 | + |
| 91 | + spawner.spawn(run_ot(ot.clone(), radio).unwrap()); |
| 92 | + |
| 93 | + info!("Dataset: {THREAD_DATASET}"); |
| 94 | + |
| 95 | + ot.set_active_dataset_tlv_hexstr(THREAD_DATASET).unwrap(); |
| 96 | + ot.enable_ipv6(true).unwrap(); |
| 97 | + ot.enable_thread(true).unwrap(); |
| 98 | + |
| 99 | + // Wait until we have attached as a child. |
| 100 | + loop { |
| 101 | + let role = ot.device_role(); |
| 102 | + info!("Role: {role:?} (eligible: {})", ot.router_eligible()); |
| 103 | + if matches!( |
| 104 | + role, |
| 105 | + DeviceRole::Child | DeviceRole::Router | DeviceRole::Leader |
| 106 | + ) { |
| 107 | + break; |
| 108 | + } |
| 109 | + ot.wait_changed().await; |
| 110 | + } |
| 111 | + |
| 112 | + // Force the router upgrade. If we are already a router/leader this is a |
| 113 | + // harmless no-op error; if we are a child, it kicks off the child->router |
| 114 | + // transition (which drives `otPlatRadioSetAlternateShortAddress`). |
| 115 | + if matches!(ot.device_role(), DeviceRole::Child) { |
| 116 | + info!("Child attached — forcing router upgrade via become_router()..."); |
| 117 | + match ot.become_router() { |
| 118 | + Ok(()) => info!("become_router(): Address Solicit sent"), |
| 119 | + Err(e) => warn!("become_router() failed: {e:?}"), |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + // Report every subsequent role change. |
| 124 | + loop { |
| 125 | + ot.wait_changed().await; |
| 126 | + info!("Role now: {:?}", ot.device_role()); |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +#[embassy_executor::task] |
| 131 | +async fn run_ot( |
| 132 | + ot: OpenThread<'static>, |
| 133 | + radio: SpinelRadio<'static, UartSpinelTransport<'static, SerialPort>>, |
| 134 | +) -> ! { |
| 135 | + ot.run(radio).await |
| 136 | +} |
0 commit comments