diff --git a/openthread-sys/src/include/riscv32imac-unknown-none-elf.rs b/openthread-sys/src/include/riscv32imac-unknown-none-elf.rs index 8286577..40c6fed 100644 --- a/openthread-sys/src/include/riscv32imac-unknown-none-elf.rs +++ b/openthread-sys/src/include/riscv32imac-unknown-none-elf.rs @@ -2830,7 +2830,7 @@ pub type _bindgen_ty_4 = ::core::ffi::c_uint; /// Represents radio capabilities. /// /// The value is a bit-field indicating the capabilities supported by the radio. See `OT_RADIO_CAPS_*` definitions. -pub type otRadioCaps = u8; +pub type otRadioCaps = u16; ///< Radio supports no capability. pub const OT_RADIO_CAPS_NONE: _bindgen_ty_5 = 0; ///< Radio supports AckTime event. @@ -2849,6 +2849,8 @@ pub const OT_RADIO_CAPS_TRANSMIT_SEC: _bindgen_ty_5 = 32; pub const OT_RADIO_CAPS_TRANSMIT_TIMING: _bindgen_ty_5 = 64; ///< Radio supports rx at specific time. pub const OT_RADIO_CAPS_RECEIVE_TIMING: _bindgen_ty_5 = 128; +///< Radio supports RxOnWhenIdle setting. +pub const OT_RADIO_CAPS_RX_ON_WHEN_IDLE: _bindgen_ty_5 = 256; /// Defines constants that are used to indicate different radio capabilities. See `otRadioCaps`. pub type _bindgen_ty_5 = ::core::ffi::c_uint; /// Represents the IEEE 802.15.4 PAN ID. @@ -3729,6 +3731,16 @@ unsafe extern "C" { /// @param[in] aEnable TRUE to enable or FALSE to disable promiscuous mode. pub fn otPlatRadioSetPromiscuous(aInstance: *mut otInstance, aEnable: bool); } +unsafe extern "C" { + /// Enable/Disable RxOnWhenIdle mode. + /// + /// If a platform supports `OT_RADIO_CAPS_RX_ON_WHEN_IDLE` it must also support `OT_RADIO_CAPS_CSMA_BACKOFF` and handle + /// tx requests from sleepy children. + /// + /// @param[in] aInstance The OpenThread instance structure. + /// @param[in] aEnable TRUE to keep radio in Receive state, FALSE to put to Sleep state during idle periods. + pub fn otPlatRadioSetRxOnWhenIdle(aInstance: *mut otInstance, aEnable: bool); +} unsafe extern "C" { /// Update MAC keys and key index /// diff --git a/openthread/src/lib.rs b/openthread/src/lib.rs index 47e471f..910bffa 100644 --- a/openthread/src/lib.rs +++ b/openthread/src/lib.rs @@ -72,12 +72,13 @@ use sys::{ otError_OT_ERROR_NONE, otError_OT_ERROR_NOT_FOUND, otError_OT_ERROR_NO_ACK, otError_OT_ERROR_NO_BUFS, otInstance, otInstanceFinalize, otInstanceInitSingle, otIp6Address, otIp6GetUnicastAddresses, otIp6IsEnabled, otIp6NewMessageFromBuffer, otIp6Send, - otIp6SetEnabled, otIp6SetReceiveCallback, otMessage, otMessageFree, + otIp6SetEnabled, otIp6SetReceiveCallback, otLinkModeConfig, otMessage, otMessageFree, otMessagePriority_OT_MESSAGE_PRIORITY_NORMAL, otMessageRead, otMessageSettings, otOperationalDataset, otOperationalDatasetTlvs, otPlatAlarmMilliFired, otPlatRadioReceiveDone, otPlatRadioTxDone, otPlatRadioTxStarted, otRadioCaps, otRadioFrame, otSetStateChangedCallback, otTaskletsProcess, otThreadGetDeviceRole, otThreadGetExtendedPanId, otThreadSetEnabled, - OT_RADIO_CAPS_ACK_TIMEOUT, OT_RADIO_FRAME_MAX_SIZE, + otThreadSetLinkMode, OT_RADIO_CAPS_ACK_TIMEOUT, OT_RADIO_CAPS_CSMA_BACKOFF, + OT_RADIO_FRAME_MAX_SIZE, }; /// A newtype wrapper over the native OpenThread error type (`otError`). @@ -426,6 +427,54 @@ impl<'a> OpenThread<'a> { ot!(unsafe { otThreadSetEnabled(state.ot.instance, enable) }) } + /// Set the Thread link mode configuration. + /// + /// Arguments: + /// - `rx_on_when_idle`: If true, the device keeps its receiver on when idle. + /// This is required for devices that need to receive unsolicited messages. + /// - `device_type`: If true, the device is a Full Thread Device (FTD). + /// - `network_data`: If true, the device requests full Network Data. + pub fn set_link_mode( + &self, + rx_on_when_idle: bool, + device_type: bool, + network_data: bool, + ) -> Result<(), OtError> { + let mut ot = self.activate(); + let state = ot.state(); + + // Update radio config rx_when_idle to match link mode. + // OpenThread doesn't call otPlatRadioSetRxOnWhenIdle, so we must + // update the radio config directly here to ensure the ESP radio + // driver keeps the radio awake when needed. + // + // IMPORTANT: Only update radio_conf when device is already connected. + // set_link_mode() is called BEFORE attach in thread.rs, so we must + // not change rx_when_idle until the device is actually connected, + // otherwise the ESP radio driver behaves differently and attach fails. + let device_role: DeviceRole = + unsafe { otThreadGetDeviceRole(state.ot.instance) }.into(); + + if device_role.is_connected() && state.ot.radio_conf.rx_when_idle != rx_on_when_idle { + info!( + "Updating radio config rx_when_idle: {} -> {}", + state.ot.radio_conf.rx_when_idle, rx_on_when_idle + ); + state.ot.radio_conf.rx_when_idle = rx_on_when_idle; + } + + let mode = otLinkModeConfig { + _bitfield_align_1: [], + _bitfield_1: otLinkModeConfig::new_bitfield_1( + rx_on_when_idle, + device_type, + network_data, + ), + }; + + ot!(unsafe { otThreadSetLinkMode(state.ot.instance, mode) }) + } + /// Gets the list of IPv6 addresses currently assigned to the Thread interface /// /// Arguments: @@ -1292,12 +1341,21 @@ impl<'a> OtContext<'a> { #[cfg(feature = "srp")] unsafe extern "C" fn plat_c_srp_state_change_callback( - _error: otError, + error: otError, host_info: *const crate::sys::otSrpClientHostInfo, services: *const crate::sys::otSrpClientService, removed_services: *const crate::sys::otSrpClientService, context: *mut c_void, ) { + // Log SRP errors for debugging (OT_ERROR_NONE = 0) + if error != 0 { + let host_state = unsafe { (*host_info).mState }; + warn!( + "SRP callback error: code={}, host_state={}", + error, host_state + ); + } + let instance = context as *mut otInstance; Self::callback(instance).plat_srp_changed( @@ -1398,7 +1456,7 @@ impl<'a> OtContext<'a> { } fn plat_radio_caps(&mut self) -> otRadioCaps { - let caps = OT_RADIO_CAPS_ACK_TIMEOUT as _; + let caps = (OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_CSMA_BACKOFF) as _; trace!("Plat radio caps callback, caps: {}", caps); caps @@ -1462,6 +1520,19 @@ impl<'a> OtContext<'a> { } } + fn plat_radio_set_rx_on_when_idle(&mut self, enable: bool) { + info!( + "Plat radio set rx_on_when_idle callback, enable: {}", + enable + ); + + let state = self.state(); + + if state.ot.radio_conf.rx_when_idle != enable { + state.ot.radio_conf.rx_when_idle = enable; + } + } + fn plat_radio_set_extended_address(&mut self, address: u64) { info!( "Plat radio set extended address callback, addr: 0x{:08x}", diff --git a/openthread/src/platform.rs b/openthread/src/platform.rs index 7a38fb8..357cbc2 100644 --- a/openthread/src/platform.rs +++ b/openthread/src/platform.rs @@ -7,7 +7,7 @@ use portable_atomic::AtomicUsize; use openthread_sys::otError_OT_ERROR_NONE; -use crate::sys::{otError, otInstance, otLogLevel, otLogRegion, otRadioFrame}; +use crate::sys::{otError, otInstance, otLogLevel, otLogRegion, otRadioCaps, otRadioFrame}; use crate::{IntoOtCode, OtActiveState, OtContext}; /// A hack so that we can store a mutable reference to the active state in a global static variable @@ -70,7 +70,7 @@ extern "C" fn otPlatRadioGetIeeeEui64(instance: *const otInstance, mac: *mut u8) } #[no_mangle] -extern "C" fn otPlatRadioGetCaps(instance: *const otInstance) -> u8 { +extern "C" fn otPlatRadioGetCaps(instance: *const otInstance) -> otRadioCaps { OtContext::callback(instance).plat_radio_caps() } @@ -105,6 +105,11 @@ extern "C" fn otPlatRadioSetPromiscuous(instance: *const otInstance, enable: boo OtContext::callback(instance).plat_radio_set_promiscuous(enable) } +#[no_mangle] +extern "C" fn otPlatRadioSetRxOnWhenIdle(instance: *const otInstance, enable: bool) { + OtContext::callback(instance).plat_radio_set_rx_on_when_idle(enable) +} + #[no_mangle] extern "C" fn otPlatRadioGetRssi(instance: *const otInstance) -> i8 { OtContext::callback(instance).plat_radio_get_rssi()