Skip to content
14 changes: 13 additions & 1 deletion openthread-sys/src/include/riscv32imac-unknown-none-elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is autogenerated, so I wonder from where these changes came, given that I don't see anything related to the OpenThread build configuration that had changed.

Did you change it manually?

///< Radio supports no capability.
pub const OT_RADIO_CAPS_NONE: _bindgen_ty_5 = 0;
///< Radio supports AckTime event.
Expand All @@ -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.
Expand Down Expand Up @@ -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
///
Expand Down
79 changes: 75 additions & 4 deletions openthread/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down Expand Up @@ -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,
Copy link
Collaborator

@ivmarkov ivmarkov Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this an enum? device_type = true meaning this is a FTD is confusing. Or - at the very least, perhaps this should be named full_thread_device?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: what would happen if OpenThread is not compiled with FTD support (which is currently the case) and I pass true?

network_data: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here: perhaps receive_full_network_data

) -> 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:
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 _;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't OT_RADIO_CAPS_CSMA_BACKOFF (and all others, for that matter, including OT_RADIO_RX_ON_WHEN_IDLE) be obtained from the Radio driver actually?

trace!("Plat radio caps callback, caps: {}", caps);

caps
Expand Down Expand Up @@ -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}",
Expand Down
9 changes: 7 additions & 2 deletions openthread/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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()
Expand Down
Loading