mirror of
https://github.com/espressif/openthread.git
synced 2026-11-07 15:03:49 +00:00
[sub-mac] introduce unified ReceiveAt logic for timed RX (#13491)
This commit introduces a unified `ReceiveAt` abstraction in `SubMac` to handle timed reception windows (CSL receiver, WED / Thread-Direct wakeup listener) Motivation & Benefits: Previously, CSL sample window calculation and scheduling differed between platform-offloaded timing (`OT_RADIO_CAPS_RECEIVE_TIMING`) and stack-driven timing (`SubMac` software timers): - `GetCslWindowEdges()` added `kCslReceiveTimeAhead` lead time to the window margin, only for offloaded callers to manually subtract it back out before calling `Radio::ReceiveAt()`. - Stack-driven mode relied on separate timers for sample/sleep transitions (`HandleCslReceiveOrSleep`, `HandleWedReceiveOrSleep`), leading to code duplication and inconsistent timing margins across platform vs stack-driven executions. This commit harmonizes timed reception handling: - `GetCslWindowEdges()` cleanly computes target window bounds without caller-side adjustments. - `SubMac::ReceiveAt()` provides a single, consistent API for both offloaded and stack-driven timing. - Provides a clean foundation for Thread-Direct wakeup listening (WED) and future timed reception features. Key changes: - State Machine Refactoring: Simplifies the state management adding `kStateTimedReceive`. The radio state is `kStateTimedReceive` when actively receiving in a target rx window, and `kStateSleep` otherwise. - `TimedRx` Helper Class: Encapsulates window parameters (`mStartTime`, `mDuration`, `mChannel`) and state checks (`HasStarted`, `HasEnded`). - Pending & Active Window Preemption: `UpdateTimedRxState()` manages the timed RX schedules and sets the timer accordingly. It ensures pending windows starting before an active window ends are promoted seamlessly. - Unit Test: Adds a detailed `test_sub_mac_recv_at.cpp` covering various behaviors (including edge-cases) of `ReceiveAt()` logic.
This commit is contained in:
+254
-137
@@ -93,6 +93,11 @@ void SubMac::Init(void)
|
||||
mFrameCounter = 0;
|
||||
mTimer.Stop();
|
||||
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
mActiveTimedRx.Clear();
|
||||
mPendingTimedRx.Clear();
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
CslInit();
|
||||
#endif
|
||||
@@ -197,6 +202,11 @@ Error SubMac::Disable(void)
|
||||
{
|
||||
Error error;
|
||||
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
mActiveTimedRx.Clear();
|
||||
mPendingTimedRx.Clear();
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
mCslTimer.Stop();
|
||||
#endif
|
||||
@@ -217,27 +227,38 @@ Error SubMac::Sleep(void)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
if (IsRadioSampleEnabled())
|
||||
{
|
||||
RadioSample();
|
||||
ExitNow();
|
||||
}
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
// `ProcessTimedRx()` evaluates active and pending timed RX windows.
|
||||
//
|
||||
// If the current time is within a timed reception window, it transitions
|
||||
// the state to `kStateTimedReceive`.
|
||||
//
|
||||
// `ProcessTimedRx()` does not put the radio to sleep unless we were
|
||||
// already in `kStateTimedReceive` and the RX window has ended.
|
||||
//
|
||||
// Therefore, after calling `ProcessTimedRx()`, we verify that `mState` is
|
||||
// not `kStateTimedReceive` before proceeding to transition the radio to sleep.
|
||||
|
||||
ProcessTimedRx();
|
||||
|
||||
VerifyOrExit(mState != kStateTimedReceive);
|
||||
#endif
|
||||
|
||||
// Even if the radio platform supports `kCapRxOnWhenIdle`, when
|
||||
// `SubMac::Sleep()` is explicitly called while `mRxOnWhenIdle`
|
||||
// is true, we still call `Radio::Sleep()`. This supports radio
|
||||
// validation and test scenarios where the radio is being forced
|
||||
// to sleep.
|
||||
// If the radio platform layer supports `kCapRxOnWhenIdle`, it is
|
||||
// responsible for putting the radio to sleep, so we skip calling
|
||||
// `Radio::Sleep()`.
|
||||
//
|
||||
// However, if `SubMac::Sleep()` is explicitly called while `mRxOnWhenIdle`
|
||||
// is true, we still call `Radio::Sleep()` to support test scenarios where
|
||||
// the radio is forced to sleep.
|
||||
|
||||
SetState(kStateSleep);
|
||||
|
||||
if (!RadioSupports(kCapRxOnWhenIdle) || mRxOnWhenIdle)
|
||||
{
|
||||
SuccessOrExit(error = Get<Radio::Radio>().Sleep());
|
||||
}
|
||||
|
||||
SetState(kStateSleep);
|
||||
|
||||
exit:
|
||||
LogWarnOnError(error, "Sleep()");
|
||||
return error;
|
||||
@@ -308,8 +329,8 @@ Error SubMac::Send(void)
|
||||
#endif
|
||||
case kStateSleep:
|
||||
case kStateReceive:
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
case kStateRadioSample:
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
case kStateTimedReceive:
|
||||
#endif
|
||||
break;
|
||||
|
||||
@@ -317,6 +338,8 @@ Error SubMac::Send(void)
|
||||
ExitNow(error = kErrorInvalidState);
|
||||
}
|
||||
|
||||
mTimer.Stop();
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
|
||||
if (mRadioFilterEnabled)
|
||||
{
|
||||
@@ -702,6 +725,13 @@ Error SubMac::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
|
||||
|
||||
switch (mState)
|
||||
{
|
||||
case kStateSleep:
|
||||
case kStateReceive:
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
case kStateTimedReceive:
|
||||
#endif
|
||||
break;
|
||||
|
||||
case kStateDisabled:
|
||||
case kStateCsmaBackoff:
|
||||
case kStateTransmit:
|
||||
@@ -713,15 +743,10 @@ Error SubMac::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
|
||||
#endif
|
||||
case kStateEnergyScan:
|
||||
ExitNow(error = kErrorInvalidState);
|
||||
|
||||
case kStateReceive:
|
||||
case kStateSleep:
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
case kStateRadioSample:
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
mTimer.Stop();
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
|
||||
if (mRadioFilterEnabled)
|
||||
{
|
||||
@@ -783,6 +808,202 @@ void SubMac::HandleEnergyScanDone(int8_t aMaxRssi)
|
||||
mCallbacks.EnergyScanDone(aMaxRssi);
|
||||
}
|
||||
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
|
||||
void SubMac::ReceiveAt(Radio::Time64 aStartTime, uint32_t aDuration, uint8_t aChannel)
|
||||
{
|
||||
Radio::SyncedTime now;
|
||||
TimedRx timedRx;
|
||||
|
||||
VerifyOrExit(mState != kStateDisabled);
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
|
||||
if (mRadioFilterEnabled)
|
||||
{
|
||||
ExitNow();
|
||||
}
|
||||
#endif
|
||||
|
||||
timedRx.Init(aStartTime, aDuration, aChannel);
|
||||
|
||||
now.SetToNow(Get<Radio::Radio>());
|
||||
|
||||
VerifyOrExit(!timedRx.HasEnded(now));
|
||||
|
||||
if (!ShouldHandle(kCapReceiveTiming))
|
||||
{
|
||||
timedRx.ScheduleOnRadio(Get<Radio::Radio>());
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
if (mPendingTimedRx.IsSpecified() && mPendingTimedRx.HasStarted(now) && !mPendingTimedRx.HasEnded(now))
|
||||
{
|
||||
// Before replacing `mPendingTimedRx` check if the existing one
|
||||
// should be started, and start if we can (we are in right states).
|
||||
// Otherwise copy it as `mActiveTimedRx` (resume/start it once
|
||||
// state changes and timed-rx is allowed).
|
||||
|
||||
switch (mState)
|
||||
{
|
||||
case kStateSleep:
|
||||
case kStateTimedReceive:
|
||||
StartPendingTimedRx();
|
||||
break;
|
||||
default:
|
||||
mActiveTimedRx = mPendingTimedRx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mPendingTimedRx = timedRx;
|
||||
|
||||
switch (mState)
|
||||
{
|
||||
case kStateSleep:
|
||||
case kStateTimedReceive:
|
||||
ProcessTimedRx();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void SubMac::CancelPendingReceiveAt(void)
|
||||
{
|
||||
VerifyOrExit(mPendingTimedRx.IsSpecified());
|
||||
mPendingTimedRx.Clear();
|
||||
|
||||
switch (mState)
|
||||
{
|
||||
case kStateSleep:
|
||||
case kStateTimedReceive:
|
||||
ProcessTimedRx();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void SubMac::StartPendingTimedRx(void)
|
||||
{
|
||||
if ((mState == kStateTimedReceive) && (mActiveTimedRx.GetChannel() == mPendingTimedRx.GetChannel()))
|
||||
{
|
||||
// Skip transitioning the radio if already receiving on the same
|
||||
// channel
|
||||
}
|
||||
else
|
||||
{
|
||||
IgnoreError(Get<Radio::Radio>().Receive(mPendingTimedRx.GetChannel()));
|
||||
}
|
||||
|
||||
mActiveTimedRx = mPendingTimedRx;
|
||||
SetState(kStateTimedReceive);
|
||||
}
|
||||
|
||||
void SubMac::ProcessTimedRx(void)
|
||||
{
|
||||
// Processes the timed RX state machine, starting any due pending
|
||||
// timed RX, scheduling the timer for upcoming windows, or putting
|
||||
// the radio to sleep when active reception window ends.
|
||||
|
||||
Radio::Time64 fireTime = Radio::kMaxTime64;
|
||||
Radio::SyncedTime now;
|
||||
|
||||
mTimer.Stop();
|
||||
|
||||
now.SetToNow(Get<Radio::Radio>());
|
||||
|
||||
// Start pending `TimedRx` if due, or clear it if missed.
|
||||
|
||||
if (mPendingTimedRx.IsSpecified())
|
||||
{
|
||||
if (mPendingTimedRx.HasStarted(now))
|
||||
{
|
||||
if (!mPendingTimedRx.HasEnded(now))
|
||||
{
|
||||
StartPendingTimedRx();
|
||||
}
|
||||
|
||||
mPendingTimedRx.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
fireTime = mPendingTimedRx.GetStartTime();
|
||||
}
|
||||
}
|
||||
|
||||
VerifyOrExit(mActiveTimedRx.IsSpecified());
|
||||
|
||||
if (!mActiveTimedRx.HasEnded(now))
|
||||
{
|
||||
if (mState != kStateTimedReceive)
|
||||
{
|
||||
IgnoreError(Get<Radio::Radio>().Receive(mActiveTimedRx.GetChannel()));
|
||||
SetState(kStateTimedReceive);
|
||||
}
|
||||
|
||||
fireTime = Min(fireTime, mActiveTimedRx.GetEndTime());
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
// Active `TimedRx` has ended. clear it and transition the radio
|
||||
// to sleep if it was actively receiving.
|
||||
|
||||
mActiveTimedRx.Clear();
|
||||
|
||||
if (mState == kStateTimedReceive)
|
||||
{
|
||||
SetState(kStateSleep);
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
|
||||
// Don't actually sleep for debugging when `MAC_CSL_DEBUG_ENABLE`.
|
||||
ExitNow();
|
||||
#endif
|
||||
IgnoreError(Get<Radio::Radio>().Sleep());
|
||||
}
|
||||
|
||||
exit:
|
||||
|
||||
if (fireTime != Radio::kMaxTime64)
|
||||
{
|
||||
// Schedule the timer to fire at a target radio time `fireTime`,
|
||||
// using the synced reference `now` to translate radio time to
|
||||
// local time.
|
||||
|
||||
uint32_t delay = 0;
|
||||
|
||||
if (fireTime > now.GetAsTime64())
|
||||
{
|
||||
delay = ClampToUint32(fireTime - now.GetAsTime64());
|
||||
}
|
||||
|
||||
StartTimerAt(now.GetAsLocalTimeMicro(), delay);
|
||||
}
|
||||
}
|
||||
|
||||
void SubMac::TimedRx::Init(Radio::Time64 aStartTime, uint32_t aDuration, uint8_t aChannel)
|
||||
{
|
||||
mStartTime = aStartTime;
|
||||
mDuration = aDuration;
|
||||
mChannel = aChannel;
|
||||
mIsSpecified = true;
|
||||
}
|
||||
|
||||
void SubMac::TimedRx::ScheduleOnRadio(Radio::Radio &aRadio) const
|
||||
{
|
||||
Error error = aRadio.ReceiveAt(mChannel, Radio::ConvertTime64To32(mStartTime), mDuration);
|
||||
|
||||
LogWarnOnError(error, "Radio::ReceiveAt()");
|
||||
}
|
||||
|
||||
#endif // OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
|
||||
void SubMac::HandleTimer(void)
|
||||
{
|
||||
switch (mState)
|
||||
@@ -810,6 +1031,13 @@ void SubMac::HandleTimer(void)
|
||||
SampleRssi();
|
||||
break;
|
||||
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
case kStateTimedReceive:
|
||||
case kStateSleep:
|
||||
ProcessTimedRx();
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -957,117 +1185,6 @@ void SubMac::StartTimerAt(Time aStartTime, uint32_t aDelayUs)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
|
||||
void SubMac::RadioSample(void)
|
||||
{
|
||||
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
|
||||
if (mRadioFilterEnabled)
|
||||
{
|
||||
IgnoreError(Get<Radio::Radio>().Sleep());
|
||||
ExitNow();
|
||||
}
|
||||
#endif
|
||||
|
||||
SetState(kStateRadioSample);
|
||||
|
||||
if (!RadioSupports(kCapReceiveTiming))
|
||||
{
|
||||
UpdateRadioSampleState();
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
|
||||
// Don't sleep for debugging when `MAC_CSL_DEBUG_ENABLE`.
|
||||
ExitNow();
|
||||
#endif
|
||||
|
||||
if (!RadioSupports(kCapRxOnWhenIdle))
|
||||
{
|
||||
IgnoreError(Get<Radio::Radio>().Sleep());
|
||||
}
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
bool SubMac::IsRadioSampleEnabled(void) const
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
ret = IsCslEnabled();
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
ret = ret || mIsWedEnabled;
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The radio state (receive/sleep) is determined by the request from both CSL and WED:
|
||||
* 1. If both CSL and WED request to enter sleep state, the radio is set to sleep state.
|
||||
* 2. If either CSL or WED requests to enter the receive state and the other requests to enter sleep state, the radio
|
||||
* is set to receive state using the channel that is requested to enter the receive state.
|
||||
* 3. If both CSL and WED request to enter the receive state, the radio is set to the receive state using the CSL
|
||||
* channel.
|
||||
*
|
||||
* The diagram below illustrates how to set the radio state based on the request of WED and CSL.
|
||||
*
|
||||
* CSL ------========------------========------------========------------========---
|
||||
* ^ ^
|
||||
* | |
|
||||
* | mIsCslSampling=false
|
||||
* mIsCslSampling=true
|
||||
*
|
||||
* WED -----------++++++++----------------++++++++----------------++++++++----------
|
||||
* ^ ^
|
||||
* | |
|
||||
* | mIsWedSampling=false
|
||||
* mIsWedSampling=true
|
||||
*
|
||||
* Radio ------========+++++-------========-++++++++---========-----+++++++========---
|
||||
* ^ ^ ^
|
||||
* | | |
|
||||
* | | Radio::Sleep()
|
||||
* | Radio::Receive(WedCh)
|
||||
* Radio::Receive(CslCh)
|
||||
*/
|
||||
void SubMac::UpdateRadioSampleState(void)
|
||||
{
|
||||
VerifyOrExit(mState == kStateRadioSample);
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
if (mIsCslSampling)
|
||||
{
|
||||
IgnoreError(Get<Radio::Radio>().Receive(mCslChannel));
|
||||
ExitNow();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
if (mIsWedSampling)
|
||||
{
|
||||
IgnoreError(Get<Radio::Radio>().Receive(mWakeupChannel));
|
||||
ExitNow();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
|
||||
// Don't sleep for debugging when `MAC_CSL_DEBUG_ENABLE`.
|
||||
ExitNow();
|
||||
#endif
|
||||
|
||||
IgnoreError(Get<Radio::Radio>().Sleep());
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
|
||||
// LCOV_EXCL_START
|
||||
|
||||
const char *SubMac::StateToString(State aState)
|
||||
@@ -1079,7 +1196,7 @@ const char *SubMac::StateToString(State aState)
|
||||
_(kStateCsmaBackoff, "CsmaBackoff") \
|
||||
_(kStateTransmit, "Transmit") \
|
||||
_(kStateEnergyScan, "EnergyScan") \
|
||||
DelayBeforeRetxStateMapList(_) TimedTxStateMapList(_) RadioSampleMapList(_)
|
||||
DelayBeforeRetxStateMapList(_) TimedTxStateMapList(_) TimedRxStateMapList(_)
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
|
||||
#define DelayBeforeRetxStateMapList(_) _(kStateDelayBeforeRetx, "DelayBeforeRetx")
|
||||
@@ -1093,10 +1210,10 @@ const char *SubMac::StateToString(State aState)
|
||||
#define TimedTxStateMapList(_)
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
#define RadioSampleMapList(_) _(kStateRadioSample, "RadioSample")
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
#define TimedRxStateMapList(_) _(kStateTimedReceive, "TimedReceive")
|
||||
#else
|
||||
#define RadioSampleMapList(_)
|
||||
#define TimedRxStateMapList(_)
|
||||
#endif
|
||||
|
||||
DefineEnumStringArray(StateMapList);
|
||||
|
||||
+73
-28
@@ -41,6 +41,7 @@
|
||||
#include <openthread/platform/crypto.h>
|
||||
|
||||
#include "common/callback.hpp"
|
||||
#include "common/clearable.hpp"
|
||||
#include "common/locator.hpp"
|
||||
#include "common/non_copyable.hpp"
|
||||
#include "common/timer.hpp"
|
||||
@@ -71,6 +72,14 @@ namespace Mac {
|
||||
(OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_INITIATOR_ENABLE || \
|
||||
((OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE) && OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE))
|
||||
|
||||
#ifdef OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
#error "OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE MUST NOT be defined directly. It is derived from other configs"
|
||||
#endif
|
||||
|
||||
#define OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE \
|
||||
(OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE || \
|
||||
((OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE) && OPENTHREAD_CONFIG_MAC_SOFTWARE_RX_TIMING_ENABLE))
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
// Config validity checks
|
||||
|
||||
@@ -78,17 +87,13 @@ namespace Mac {
|
||||
#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE."
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
|
||||
#if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
|
||||
#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE."
|
||||
#endif
|
||||
|
||||
#if !OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE && !OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE
|
||||
#error "Microsecond timer OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE is required for "\
|
||||
"OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE"
|
||||
#endif
|
||||
|
||||
"TARGET TIME RX feature (CSL, Thread direct wakeup listener, etc)."
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
@@ -338,6 +343,28 @@ public:
|
||||
*/
|
||||
Error Receive(uint8_t aChannel);
|
||||
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
/**
|
||||
* Schedules a radio reception window at a specific time and duration.
|
||||
*
|
||||
* `SubMac` supports one active and one pending reception window. If an unstarted receive window is already
|
||||
* pending when this method is called, the new window will replace the existing pending window.
|
||||
*
|
||||
* @param[in] aStartTime The start time in microseconds.
|
||||
* @param[in] aDuration The duration of the receive window in microseconds.
|
||||
* @param[in] aChannel The channel to use for receiving.
|
||||
*/
|
||||
void ReceiveAt(Radio::Time64 aStartTime, uint32_t aDuration, uint8_t aChannel);
|
||||
|
||||
/**
|
||||
* Cancels any pending scheduled receive window (`ReceiveAt()`).
|
||||
*
|
||||
* If a scheduled reception window is currently pending (waiting to start), it is cancelled. If a timed reception
|
||||
* window is already active (radio is currently receiving), this method does not interrupt the ongoing reception.
|
||||
*/
|
||||
void CancelPendingReceiveAt(void);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Gets the radio transmit frame.
|
||||
*
|
||||
@@ -552,9 +579,8 @@ private:
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE
|
||||
kStateTimedTransmit, // Timed TX (e.g., for CSL)
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
kStateRadioSample, // Mac layer has requested the SubMac to enter sleep state, but the SubMac is in the periodic
|
||||
// sample state.
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
kStateTimedReceive, // Timed RX (CSL sampling or wake listening)
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -582,6 +608,29 @@ private:
|
||||
OPENTHREAD_CONFIG_CSL_TRANSMIT_TIME_AHEAD + kCcaSampleInterval + Radio::kHeaderShrDuration;
|
||||
#endif
|
||||
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
class TimedRx : public Clearable<TimedRx>
|
||||
{
|
||||
public:
|
||||
TimedRx(void) { Clear(); }
|
||||
|
||||
void Init(Radio::Time64 aStartTime, uint32_t aDuration, uint8_t aChannel);
|
||||
bool IsSpecified(void) const { return mIsSpecified; }
|
||||
Radio::Time64 GetStartTime(void) const { return mStartTime; }
|
||||
Radio::Time64 GetEndTime(void) const { return mStartTime + mDuration; }
|
||||
uint8_t GetChannel(void) const { return mChannel; }
|
||||
bool HasStarted(const Radio::SyncedTime &aNow) const { return mStartTime <= aNow.GetAsTime64(); }
|
||||
bool HasEnded(const Radio::SyncedTime &aNow) const { return GetEndTime() <= aNow.GetAsTime64(); }
|
||||
void ScheduleOnRadio(Radio::Radio &aRadio) const;
|
||||
|
||||
private:
|
||||
Radio::Time64 mStartTime;
|
||||
uint32_t mDuration;
|
||||
uint8_t mChannel;
|
||||
bool mIsSpecified;
|
||||
};
|
||||
#endif
|
||||
|
||||
void Init(void);
|
||||
|
||||
bool RadioSupports(Capability aCapability) const { return (mRadioCaps & aCapability) != 0; }
|
||||
@@ -605,15 +654,14 @@ private:
|
||||
void HandleEnergyScanDone(int8_t aMaxRssi);
|
||||
void HandleTimer(void);
|
||||
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
void StartPendingTimedRx(void);
|
||||
void ProcessTimedRx(void);
|
||||
#endif
|
||||
|
||||
void SetState(State aState);
|
||||
static const char *StateToString(State aState);
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
bool IsRadioSampleEnabled(void) const;
|
||||
void UpdateRadioSampleState(void);
|
||||
void RadioSample(void);
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
void CslInit(void);
|
||||
void RestartCslTimerAfterSyncUpdate(void);
|
||||
@@ -625,19 +673,15 @@ private:
|
||||
uint32_t DetermineClockDrift(uint32_t aIntervalUs) const;
|
||||
uint32_t GetNextCycleDrift(void) const;
|
||||
bool IsCslEnabled(void) const { return mCslPeriod > 0; }
|
||||
void LogCslWindow(Radio::Time64 aStart, uint32_t aDuration);
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
|
||||
void LogReceived(RxFrame *aFrame);
|
||||
#endif
|
||||
void HandleCslReceiveAt(uint32_t aTimeAhead, uint32_t aTimeAfter);
|
||||
void HandleCslReceiveOrSleep(uint32_t aTimeAhead, uint32_t aTimeAfter);
|
||||
void LogCslWindow(uint32_t aWinStart, uint32_t aWinDuration);
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
void WedInit(void);
|
||||
void HandleWedTimer(void);
|
||||
void HandleWedReceiveAt(void);
|
||||
void HandleWedReceiveOrSleep(void);
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE
|
||||
@@ -667,15 +711,18 @@ private:
|
||||
#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
|
||||
uint8_t mRetxDelayBackoffExponent;
|
||||
#endif
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
TimedRx mActiveTimedRx;
|
||||
TimedRx mPendingTimedRx;
|
||||
#endif
|
||||
|
||||
SubMacTimer mTimer;
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
using CslTimer = TimerMicroIn<SubMac, &SubMac::HandleCslTimer>;
|
||||
|
||||
uint16_t mCslPeriod; // The CSL sample period, in units of 10 symbols (160 microseconds).
|
||||
uint8_t mCslChannel : 7; // The CSL sample channel.
|
||||
bool mIsCslSampling : 1; // Indicates that the current time is in CSL sample window
|
||||
// for platforms not supporting `Radio::ReceiveAt()`.
|
||||
uint16_t mCslPeriod; // The CSL sample period, in units of 10 symbols (160 microseconds).
|
||||
uint8_t mCslChannel; // The CSL sample channel.
|
||||
uint16_t mCslPeerShort; // The CSL peer short address.
|
||||
Radio::SyncedTime mCslSampleTime; // The CSL sample time for current period.
|
||||
CslAccuracy mCslParentAccuracy; // The parent's CSL accuracy (clock accuracy and uncertainty).
|
||||
@@ -690,9 +737,7 @@ private:
|
||||
#if OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
using WedTimer = TimerMicroIn<SubMac, &SubMac::HandleWedTimer>;
|
||||
|
||||
bool mIsWedSampling : 1; // Indicates that the current time is in WED's sample window
|
||||
// for platforms not supporting `Radio::ReceiveAt()`.
|
||||
bool mIsWedEnabled : 1; // Indicates if the WED is enabled.
|
||||
bool mIsWedEnabled; // Indicates if the WED is enabled.
|
||||
uint32_t mWakeupListenInterval; // The wake-up listen interval, in microseconds.
|
||||
uint32_t mWakeupListenDuration; // The wake-up listen duration, in microseconds.
|
||||
uint8_t mWakeupChannel; // The wake-up sample channel.
|
||||
|
||||
@@ -44,10 +44,9 @@ RegisterLogModule("SubMac");
|
||||
|
||||
void SubMac::CslInit(void)
|
||||
{
|
||||
mCslPeriod = 0;
|
||||
mCslChannel = 0;
|
||||
mCslPeerShort = 0;
|
||||
mIsCslSampling = false;
|
||||
mCslPeriod = 0;
|
||||
mCslChannel = 0;
|
||||
mCslPeerShort = 0;
|
||||
mCslSampleTime.Clear();
|
||||
mCslTimer.Stop();
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
|
||||
@@ -59,8 +58,7 @@ void SubMac::CslInit(void)
|
||||
|
||||
void SubMac::RestartCslTimerAfterSyncUpdate(void)
|
||||
{
|
||||
// Only applies for the case where radio supports receive timing.
|
||||
if (RadioSupports(kCapReceiveTiming) && mCslTimer.IsRunning())
|
||||
if (mCslTimer.IsRunning())
|
||||
{
|
||||
uint32_t periodUs = CslPeriodToUsec(mCslPeriod);
|
||||
|
||||
@@ -118,8 +116,7 @@ void SubMac::SetCslParams(uint16_t aPeriod, uint8_t aChannel, ShortAddress aShor
|
||||
mCslPeerShort = aShortAddr;
|
||||
IgnoreError(Get<Radio::Radio>().EnableCsl(aPeriod, aShortAddr, aExtAddr));
|
||||
|
||||
mIsCslSampling = false;
|
||||
mCslPeriod = aPeriod;
|
||||
mCslPeriod = aPeriod;
|
||||
|
||||
mCslTimer.Stop();
|
||||
|
||||
@@ -134,9 +131,9 @@ void SubMac::SetCslParams(uint16_t aPeriod, uint8_t aChannel, ShortAddress aShor
|
||||
#endif
|
||||
HandleCslTimer();
|
||||
}
|
||||
else if (!RadioSupports(kCapReceiveTiming))
|
||||
else
|
||||
{
|
||||
UpdateRadioSampleState();
|
||||
CancelPendingReceiveAt();
|
||||
}
|
||||
|
||||
exit:
|
||||
@@ -144,32 +141,11 @@ exit:
|
||||
}
|
||||
|
||||
void SubMac::HandleCslTimer(void)
|
||||
{
|
||||
uint32_t timeAhead, timeAfter;
|
||||
|
||||
GetCslWindowEdges(timeAhead, timeAfter);
|
||||
|
||||
// The handler works in different ways when the radio supports receive-timing and doesn't.
|
||||
if (RadioSupports(kCapReceiveTiming))
|
||||
{
|
||||
HandleCslReceiveAt(timeAhead, timeAfter);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleCslReceiveOrSleep(timeAhead, timeAfter);
|
||||
}
|
||||
}
|
||||
|
||||
void SubMac::HandleCslReceiveAt(uint32_t aTimeAhead, uint32_t aTimeAfter)
|
||||
{
|
||||
/*
|
||||
* When the radio supports receive-timing:
|
||||
* The handler will be called once per CSL period. When the handler is called, it will set the timer to
|
||||
* fire at the next CSL sample time and call `Radio::ReceiveAt` to start sampling for the current CSL period.
|
||||
* The timer fires some time before the actual sample time. After `Radio::ReceiveAt` is called, the radio will
|
||||
* remain in sleep state until the actual sample time.
|
||||
* Note that it never call `Radio::Sleep` explicitly. The radio will fall into sleep after `ReceiveAt` ends. This
|
||||
* will be done by the platform as part of the `otPlatRadioReceiveAt` API.
|
||||
* fire at the next CSL sample time and call `ReceiveAt` to start sampling for the current CSL period.
|
||||
* The timer fires some time before the next sample time.
|
||||
*
|
||||
* Timer fires Timer fires
|
||||
* ^ ^
|
||||
@@ -177,73 +153,27 @@ void SubMac::HandleCslReceiveAt(uint32_t aTimeAhead, uint32_t aTimeAfter)
|
||||
* sample sleep sample sleep
|
||||
*/
|
||||
uint32_t periodUs = CslPeriodToUsec(mCslPeriod);
|
||||
Radio::Time32 winStart;
|
||||
uint32_t timeAhead, timeAfter;
|
||||
Radio::Time64 winStart;
|
||||
uint32_t winDuration;
|
||||
|
||||
mCslTimer.FireAt(mCslSampleTime.GetAsLocalTimeMicro() + periodUs - aTimeAhead - GetNextCycleDrift());
|
||||
aTimeAhead -= kCslReceiveTimeAhead;
|
||||
winStart = mCslSampleTime.GetAsTime32() - aTimeAhead;
|
||||
winDuration = aTimeAhead + aTimeAfter;
|
||||
GetCslWindowEdges(timeAhead, timeAfter);
|
||||
|
||||
mCslTimer.FireAt(mCslSampleTime.GetAsLocalTimeMicro() + periodUs - timeAhead - kCslReceiveTimeAhead -
|
||||
GetNextCycleDrift());
|
||||
|
||||
winStart = mCslSampleTime.GetAsTime64() - timeAhead;
|
||||
winDuration = timeAhead + timeAfter;
|
||||
|
||||
mCslSampleTime += periodUs;
|
||||
|
||||
Get<Radio::Radio>().UpdateCslSampleTime(mCslSampleTime.GetAsTime32());
|
||||
|
||||
// Schedule reception window for any state except RX - so that CSL RX Window has lower priority
|
||||
// than scanning or RX after the data poll.
|
||||
if ((mState != kStateDisabled) && (mState != kStateReceive) &&
|
||||
Radio::IsTimeStrictlyBefore(Get<Radio::Radio>().GetNowAsTime32(), winStart + winDuration))
|
||||
{
|
||||
IgnoreError(Get<Radio::Radio>().ReceiveAt(mCslChannel, winStart, winDuration));
|
||||
}
|
||||
ReceiveAt(winStart, winDuration, mCslChannel);
|
||||
|
||||
LogCslWindow(winStart, winDuration);
|
||||
}
|
||||
|
||||
void SubMac::HandleCslReceiveOrSleep(uint32_t aTimeAhead, uint32_t aTimeAfter)
|
||||
{
|
||||
/*
|
||||
* When the radio doesn't support receive-timing:
|
||||
* The handler will be called twice per CSL period: at the beginning of sample and sleep. When the handler is
|
||||
* called, it will explicitly change the radio state due to the current state by calling `Radio::Receive` or
|
||||
* `Radio::Sleep`.
|
||||
*
|
||||
* Timer fires Timer fires Timer fires Timer fires
|
||||
* ^ ^ ^ ^
|
||||
* |------------|---------------------------------------|------------|---------------------------------------|
|
||||
* sample sleep sample sleep
|
||||
*/
|
||||
|
||||
if (mIsCslSampling)
|
||||
{
|
||||
mIsCslSampling = false;
|
||||
mCslTimer.FireAt(mCslSampleTime.GetAsLocalTimeMicro() - aTimeAhead - GetNextCycleDrift());
|
||||
if (mState == kStateRadioSample)
|
||||
{
|
||||
LogDebg("CSL sleep %lu", ToUlong(mCslTimer.GetNow().GetValue()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t periodUs = CslPeriodToUsec(mCslPeriod);
|
||||
uint32_t winStart;
|
||||
uint32_t winDuration;
|
||||
|
||||
mCslTimer.FireAt(mCslSampleTime.GetAsLocalTimeMicro() + aTimeAfter);
|
||||
mIsCslSampling = true;
|
||||
winStart = TimerMicro::GetNow().GetValue();
|
||||
winDuration = aTimeAhead + aTimeAfter;
|
||||
|
||||
mCslSampleTime += periodUs;
|
||||
|
||||
Get<Radio::Radio>().UpdateCslSampleTime(mCslSampleTime.GetAsTime32());
|
||||
|
||||
LogCslWindow(winStart, winDuration);
|
||||
}
|
||||
|
||||
UpdateRadioSampleState();
|
||||
}
|
||||
|
||||
void SubMac::GetCslWindowEdges(uint32_t &aAhead, uint32_t &aAfter)
|
||||
{
|
||||
/*
|
||||
@@ -275,7 +205,7 @@ void SubMac::GetCslWindowEdges(uint32_t &aAhead, uint32_t &aAfter)
|
||||
semiWindow +=
|
||||
Radio::ConvertUncertaintyToUsec(mCslParentAccuracy.GetUncertainty() + Get<Radio::Radio>().GetCslUncertainty());
|
||||
|
||||
aAhead = Min(semiPeriod, semiWindow + kMinReceiveOnAhead + kCslReceiveTimeAhead);
|
||||
aAhead = Min(semiPeriod, semiWindow + kMinReceiveOnAhead);
|
||||
aAfter = Min(semiPeriod, semiWindow + kMinReceiveOnAfter);
|
||||
}
|
||||
|
||||
@@ -298,12 +228,12 @@ void SubMac::SetCslLastSyncToNow(void)
|
||||
}
|
||||
|
||||
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_DEBG)
|
||||
void SubMac::LogCslWindow(uint32_t aWinStart, uint32_t aWinDuration)
|
||||
void SubMac::LogCslWindow(Radio::Time64 aWinStart, uint32_t aWinDuration)
|
||||
{
|
||||
LogDebg("CSL window start %lu, duration %lu", ToUlong(aWinStart), ToUlong(aWinDuration));
|
||||
LogDebg("CSL window start %lu, duration %lu", ToUlong(Radio::ConvertTime64To32(aWinStart)), ToUlong(aWinDuration));
|
||||
}
|
||||
#else
|
||||
void SubMac::LogCslWindow(uint32_t, uint32_t) {}
|
||||
void SubMac::LogCslWindow(Radio::Time64, uint32_t) {}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
|
||||
@@ -321,13 +251,13 @@ void SubMac::LogReceived(RxFrame *aFrame)
|
||||
VerifyOrExit((dst.GetType() == Address::kTypeShort && dst.GetShort() == GetShortAddress()) ||
|
||||
(dst.GetType() == Address::kTypeExtended && dst.GetExtended() == GetExtAddress()));
|
||||
|
||||
LogDebg("Received frame in state (SubMac %s, CSL %s), timestamp %lu", StateToString(mState),
|
||||
mIsCslSampling ? "CslSample" : "CslSleep", ToUlong(Radio::ConvertTime64To32(aFrame->GetTimestamp())));
|
||||
LogDebg("Received frame in state %s, timestamp %lu", StateToString(mState),
|
||||
ToUlong(Radio::ConvertTime64To32(aFrame->GetTimestamp())));
|
||||
|
||||
VerifyOrExit(mState == kStateRadioSample);
|
||||
VerifyOrExit((mState == kStateTimedReceive) || (mState == kStateSleep));
|
||||
|
||||
GetCslWindowEdges(ahead, after);
|
||||
ahead -= kMinReceiveOnAhead + kCslReceiveTimeAhead;
|
||||
ahead -= kMinReceiveOnAhead;
|
||||
|
||||
sampleTime = mCslSampleTime.GetAsTime32() - CslPeriodToUsec(mCslPeriod);
|
||||
deviation = Radio::ConvertTime64To32(aFrame->GetTimestamp()) + Radio::kHeaderPhrDuration - sampleTime;
|
||||
|
||||
@@ -44,7 +44,6 @@ RegisterLogModule("SubMac");
|
||||
|
||||
void SubMac::WedInit(void)
|
||||
{
|
||||
mIsWedSampling = false;
|
||||
mIsWedEnabled = false;
|
||||
mWakeupListenInterval = 0;
|
||||
mWedTimer.Stop();
|
||||
@@ -55,7 +54,6 @@ void SubMac::UpdateWakeupListening(bool aEnable, uint32_t aInterval, uint32_t aD
|
||||
mWakeupListenInterval = aInterval;
|
||||
mWakeupListenDuration = aDuration;
|
||||
mWakeupChannel = aChannel;
|
||||
mIsWedSampling = false;
|
||||
mIsWedEnabled = aEnable;
|
||||
|
||||
mWedTimer.Stop();
|
||||
@@ -68,56 +66,17 @@ void SubMac::UpdateWakeupListening(bool aEnable, uint32_t aInterval, uint32_t aD
|
||||
|
||||
HandleWedTimer();
|
||||
}
|
||||
else if (!RadioSupports(kCapReceiveTiming))
|
||||
{
|
||||
UpdateRadioSampleState();
|
||||
}
|
||||
}
|
||||
|
||||
void SubMac::HandleWedTimer(void)
|
||||
{
|
||||
if (RadioSupports(kCapReceiveTiming))
|
||||
{
|
||||
HandleWedReceiveAt();
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleWedReceiveOrSleep();
|
||||
}
|
||||
}
|
||||
|
||||
void SubMac::HandleWedReceiveAt(void)
|
||||
{
|
||||
mWedSampleTime += mWakeupListenInterval;
|
||||
|
||||
mWedTimer.FireAt(mWedSampleTime.GetAsLocalTimeMicro() + mWakeupListenDuration + kWedReceiveTimeAfter);
|
||||
|
||||
if (mState != kStateDisabled)
|
||||
{
|
||||
IgnoreError(Get<Radio::Radio>().ReceiveAt(mWakeupChannel, mWedSampleTime.GetAsTime32(), mWakeupListenDuration));
|
||||
}
|
||||
ReceiveAt(mWedSampleTime.GetAsTime64(), mWakeupListenDuration, mWakeupChannel);
|
||||
}
|
||||
|
||||
void SubMac::HandleWedReceiveOrSleep(void)
|
||||
{
|
||||
TimeMilli fireTime;
|
||||
|
||||
mIsWedSampling = !mIsWedSampling;
|
||||
|
||||
if (mIsWedSampling)
|
||||
{
|
||||
fireTime = mWedSampleTime.GetAsLocalTimeMicro() + mWakeupListenDuration + kMinReceiveOnAfter;
|
||||
}
|
||||
else
|
||||
{
|
||||
mWedSampleTime += mWakeupListenInterval;
|
||||
fireTime = mWedSampleTime.GetAsLocalTimeMicro() - kMinReceiveOnAhead;
|
||||
}
|
||||
|
||||
mWedTimer.FireAt(fireTime);
|
||||
|
||||
UpdateRadioSampleState();
|
||||
}
|
||||
} // namespace Mac
|
||||
} // namespace ot
|
||||
|
||||
|
||||
@@ -573,7 +573,6 @@ public:
|
||||
*/
|
||||
Error Receive(uint8_t aChannel);
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
/**
|
||||
* Schedules a radio reception window at a specific time and duration.
|
||||
*
|
||||
@@ -585,7 +584,6 @@ public:
|
||||
* @retval kErrorFailed The receive window could not be scheduled.
|
||||
*/
|
||||
Error ReceiveAt(uint8_t aChannel, Time32 aStart, uint32_t aDuration);
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
/**
|
||||
@@ -989,7 +987,6 @@ inline Error Radio::Receive(uint8_t aChannel)
|
||||
return otPlatRadioReceive(GetInstancePtr(), aChannel);
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
inline Error Radio::ReceiveAt(uint8_t aChannel, Time32 aStart, uint32_t aDuration)
|
||||
{
|
||||
Error error = otPlatRadioReceiveAt(GetInstancePtr(), aChannel, aStart, aDuration);
|
||||
@@ -1001,7 +998,6 @@ inline Error Radio::ReceiveAt(uint8_t aChannel, Time32 aStart, uint32_t aDuratio
|
||||
#endif
|
||||
return error;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
inline void Radio::UpdateCslSampleTime(Time32 aCslSampleTime)
|
||||
@@ -1103,9 +1099,7 @@ inline Error Radio::Sleep(void) { return kErrorNone; }
|
||||
|
||||
inline Error Radio::Receive(uint8_t) { return kErrorNone; }
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE
|
||||
inline Error Radio::ReceiveAt(uint8_t, uint32_t, uint32_t) { return kErrorNone; }
|
||||
#endif
|
||||
inline Error Radio::ReceiveAt(uint8_t, uint32_t, uint32_t) { return kErrorNotImplemented; }
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
inline void Radio::UpdateCslSampleTime(Time32) {}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <openthread/platform/radio.h>
|
||||
|
||||
#include "common/clearable.hpp"
|
||||
#include "common/numeric_limits.hpp"
|
||||
#include "common/string.hpp"
|
||||
#include "common/time.hpp"
|
||||
|
||||
@@ -71,6 +72,11 @@ typedef otRadioTime64 Time64;
|
||||
*/
|
||||
typedef otRadioTime32 Time32;
|
||||
|
||||
/**
|
||||
* Represents the maximum value of a 64-bit radio time.
|
||||
*/
|
||||
constexpr Time64 kMaxTime64 = NumericLimits<Time64>::kMax;
|
||||
|
||||
/**
|
||||
* Converts a 64-bit radio time to a 32-bit radio time.
|
||||
*
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
#include "openthread-core-toranj-config.h"
|
||||
|
||||
#if OPENTHREAD_RADIO
|
||||
|
||||
#define OPENTHREAD_CONFIG_PLATFORM_INFO "SIMULATION-RCP-toranj"
|
||||
#else
|
||||
#define OPENTHREAD_CONFIG_PLATFORM_INFO "SIMULATION-toranj"
|
||||
@@ -85,7 +84,9 @@
|
||||
|
||||
#define OPENTHREAD_SIMULATION_MDNS_SOCKET_IMPLEMENT_POSIX 1
|
||||
|
||||
#define OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE 0
|
||||
#define OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE 1
|
||||
|
||||
#define OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE 1
|
||||
|
||||
#define OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE 1
|
||||
|
||||
|
||||
@@ -265,6 +265,7 @@ ot_unit_test(spinel_prop_codec)
|
||||
ot_unit_test(srp_adv_proxy)
|
||||
ot_unit_test(srp_server)
|
||||
ot_unit_test(string)
|
||||
ot_unit_test(sub_mac_recv_at)
|
||||
ot_unit_test(tasklet)
|
||||
ot_unit_test(tcat)
|
||||
ot_unit_test(timer)
|
||||
|
||||
@@ -0,0 +1,986 @@
|
||||
/*
|
||||
* Copyright (c) 2026, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "test_platform.h"
|
||||
|
||||
#include <openthread/config.h>
|
||||
|
||||
#include "instance/instance.hpp"
|
||||
|
||||
namespace ot {
|
||||
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
|
||||
enum RadioState
|
||||
{
|
||||
kRadioStateSleep,
|
||||
kRadioStateReceive,
|
||||
};
|
||||
|
||||
static uint64_t sNow = 10000000;
|
||||
static RadioState sRadioState = kRadioStateSleep;
|
||||
static uint8_t sReceiveChannel = 0;
|
||||
static uint32_t sReceiveCallCount = 0;
|
||||
static uint32_t sLocalRadioTimeDifference = 0;
|
||||
|
||||
static bool sAlarmMicroOn = false;
|
||||
static uint32_t sAlarmMicroT0 = 0;
|
||||
static uint32_t sAlarmMicroDt = 0;
|
||||
|
||||
static bool sAlarmMilliOn = false;
|
||||
static uint32_t sAlarmMilliT0 = 0;
|
||||
static uint32_t sAlarmMilliDt = 0;
|
||||
|
||||
extern "C" {
|
||||
|
||||
otRadioTime64 otPlatRadioGetNow(otInstance *) { return sNow + sLocalRadioTimeDifference; }
|
||||
|
||||
otError otPlatRadioReceive(otInstance *, uint8_t aChannel)
|
||||
{
|
||||
sRadioState = kRadioStateReceive;
|
||||
sReceiveChannel = aChannel;
|
||||
sReceiveCallCount++;
|
||||
return OT_ERROR_NONE;
|
||||
}
|
||||
|
||||
otError otPlatRadioSleep(otInstance *)
|
||||
{
|
||||
sRadioState = kRadioStateSleep;
|
||||
return OT_ERROR_NONE;
|
||||
}
|
||||
|
||||
otRadioCaps otPlatRadioGetCaps(otInstance *) { return OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_CSMA_BACKOFF; }
|
||||
|
||||
void otPlatAlarmMicroStop(otInstance *) { sAlarmMicroOn = false; }
|
||||
|
||||
void otPlatAlarmMicroStartAt(otInstance *, uint32_t aT0, uint32_t aDt)
|
||||
{
|
||||
sAlarmMicroOn = true;
|
||||
sAlarmMicroT0 = aT0;
|
||||
sAlarmMicroDt = aDt;
|
||||
}
|
||||
|
||||
uint32_t otPlatAlarmMicroGetNow(void) { return static_cast<uint32_t>(sNow); }
|
||||
|
||||
void otPlatAlarmMilliStop(otInstance *) { sAlarmMilliOn = false; }
|
||||
|
||||
void otPlatAlarmMilliStartAt(otInstance *, uint32_t aT0, uint32_t aDt)
|
||||
{
|
||||
sAlarmMilliOn = true;
|
||||
sAlarmMilliT0 = aT0;
|
||||
sAlarmMilliDt = aDt;
|
||||
}
|
||||
|
||||
uint32_t otPlatAlarmMilliGetNow(void) { return static_cast<uint32_t>(sNow / 1000); }
|
||||
|
||||
} // extern "C"
|
||||
|
||||
static void ResetPlatformState(void)
|
||||
{
|
||||
sNow = 10000000;
|
||||
sRadioState = kRadioStateSleep;
|
||||
sReceiveChannel = 0;
|
||||
sReceiveCallCount = 0;
|
||||
sAlarmMicroOn = false;
|
||||
sAlarmMicroT0 = 0;
|
||||
sAlarmMicroDt = 0;
|
||||
sAlarmMilliOn = false;
|
||||
sAlarmMilliT0 = 0;
|
||||
sAlarmMilliDt = 0;
|
||||
}
|
||||
|
||||
static void AdvanceTime(Instance &aInstance, uint32_t aDurationUs)
|
||||
{
|
||||
uint64_t targetTime = sNow + aDurationUs;
|
||||
|
||||
while (sNow < targetTime)
|
||||
{
|
||||
uint32_t step = static_cast<uint32_t>(targetTime - sNow);
|
||||
|
||||
if (sAlarmMicroOn)
|
||||
{
|
||||
uint32_t alarmFireTime = sAlarmMicroT0 + sAlarmMicroDt;
|
||||
uint32_t timeToAlarm = alarmFireTime - static_cast<uint32_t>(sNow);
|
||||
|
||||
if (timeToAlarm <= step)
|
||||
{
|
||||
step = timeToAlarm;
|
||||
}
|
||||
}
|
||||
|
||||
sNow += step;
|
||||
|
||||
if (sAlarmMicroOn && (static_cast<uint32_t>(sNow) == sAlarmMicroT0 + sAlarmMicroDt))
|
||||
{
|
||||
sAlarmMicroOn = false;
|
||||
otPlatAlarmMicroFired(&aInstance);
|
||||
}
|
||||
|
||||
if (sAlarmMilliOn && (static_cast<uint32_t>(sNow / 1000) == sAlarmMilliT0 + sAlarmMilliDt))
|
||||
{
|
||||
sAlarmMilliOn = false;
|
||||
otPlatAlarmMilliFired(&aInstance);
|
||||
}
|
||||
|
||||
otTaskletsProcess(&aInstance);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
void TestSimpleReceiveAt(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
uint64_t startTime;
|
||||
uint32_t duration;
|
||||
uint8_t channel;
|
||||
|
||||
printf("TestSimpleReceiveAt()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep);
|
||||
|
||||
// Schedule a simple future `ReceiveAt` window and verify that the
|
||||
// radio stays in sleep before the start time, transitions to receive
|
||||
// during the window, and returns to sleep when the window ends.
|
||||
|
||||
startTime = otPlatRadioGetNow(instance) + 10000;
|
||||
duration = 5000;
|
||||
channel = 11;
|
||||
|
||||
subMac->ReceiveAt(startTime, duration, channel);
|
||||
|
||||
AdvanceTime(*instance, 9999);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should be in sleep before window start");
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should enter receive at window start");
|
||||
VerifyOrQuit(sReceiveChannel == channel, "Receive channel mismatch");
|
||||
|
||||
AdvanceTime(*instance, 4999);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should stay in receive during window");
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should return to sleep at window end");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestPendingReceiveAtReplaced(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime1;
|
||||
Radio::Time64 startTime2;
|
||||
|
||||
printf("TestPendingReceiveAtReplaced()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt`, replace it before it starts.
|
||||
// Validate that the new `ReceiveAt` is executed.
|
||||
|
||||
startTime1 = otPlatRadioGetNow(instance) + 100;
|
||||
subMac->ReceiveAt(startTime1, 50, 11);
|
||||
|
||||
AdvanceTime(*instance, 20);
|
||||
|
||||
startTime2 = otPlatRadioGetNow(instance) + 200;
|
||||
subMac->ReceiveAt(startTime2, 50, 12);
|
||||
|
||||
AdvanceTime(*instance, 140);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should NOT enter receive for replaced window 1");
|
||||
|
||||
AdvanceTime(*instance, static_cast<uint32_t>(startTime2 - otPlatRadioGetNow(instance)));
|
||||
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should enter receive for window 2");
|
||||
VerifyOrQuit(sReceiveChannel == 12, "Receive channel mismatch for window 2");
|
||||
|
||||
AdvanceTime(*instance, 50);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should return to sleep after window 2");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestReceiveAtAlreadyStarted(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime;
|
||||
uint32_t duration;
|
||||
uint8_t channel;
|
||||
|
||||
printf("TestReceiveAtAlreadyStarted()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt` which is already started.
|
||||
|
||||
startTime = otPlatRadioGetNow(instance) - 2000;
|
||||
duration = 10000;
|
||||
channel = 15;
|
||||
|
||||
subMac->ReceiveAt(startTime, duration, channel);
|
||||
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should immediately enter receive for started window");
|
||||
VerifyOrQuit(sReceiveChannel == channel, "Receive channel mismatch");
|
||||
|
||||
AdvanceTime(*instance, 7999);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should remain in receive");
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should return to sleep when window ends");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestReceiveAtAlreadyExpired(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime;
|
||||
uint32_t duration;
|
||||
|
||||
printf("TestReceiveAtAlreadyExpired()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt` which is already expired.
|
||||
|
||||
startTime = otPlatRadioGetNow(instance) - 10000;
|
||||
duration = 5000;
|
||||
|
||||
subMac->ReceiveAt(startTime, duration, 11);
|
||||
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep for expired window");
|
||||
|
||||
AdvanceTime(*instance, 10000);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep");
|
||||
VerifyOrQuit(sReceiveCallCount == 0);
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestReceiveAtWhileActive(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime1;
|
||||
Radio::Time64 startTime2;
|
||||
|
||||
printf("TestReceiveAtWhileActive()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt`, wait for it to start,
|
||||
// then schedule a new one while the previous one is
|
||||
// active.
|
||||
|
||||
startTime1 = otPlatRadioGetNow(instance) + 50;
|
||||
subMac->ReceiveAt(startTime1, 100, 11);
|
||||
|
||||
AdvanceTime(*instance, 70);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should be in receive for window 1");
|
||||
|
||||
startTime2 = startTime1 + 200;
|
||||
subMac->ReceiveAt(startTime2, 50, 12);
|
||||
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should remain in receive for active window 1");
|
||||
VerifyOrQuit(sReceiveChannel == 11, "Radio should remain on channel 11");
|
||||
|
||||
AdvanceTime(*instance, 80);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after window 1 ends");
|
||||
|
||||
AdvanceTime(*instance, static_cast<uint32_t>(startTime2 - otPlatRadioGetNow(instance)));
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should enter receive for window 2");
|
||||
VerifyOrQuit(sReceiveChannel == 12, "Radio should switch to channel 12 for window 2");
|
||||
|
||||
AdvanceTime(*instance, 50);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after window 2 ends");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestSleepDuringActiveReceiveAt(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime;
|
||||
uint32_t duration;
|
||||
uint8_t channel;
|
||||
|
||||
printf("TestSleepDuringActiveReceiveAt()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt`, wait for it to start, then while in the
|
||||
// middle of the active window call `Sleep()` multiple times.
|
||||
// Ensure that explicit calls to `Sleep()` do not interrupt the
|
||||
// active timed RX window and the radio remains in receive until
|
||||
// the window ends.
|
||||
|
||||
startTime = otPlatRadioGetNow(instance) + 50;
|
||||
duration = 50;
|
||||
channel = 11;
|
||||
subMac->ReceiveAt(startTime, duration, channel);
|
||||
|
||||
AdvanceTime(*instance, 50);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == channel);
|
||||
|
||||
AdvanceTime(*instance, 20);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == channel);
|
||||
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Sleep() during active window should not stop timed-rx");
|
||||
VerifyOrQuit(sReceiveChannel == channel);
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == channel);
|
||||
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Subsequent Sleep() should not stop timed-rx");
|
||||
VerifyOrQuit(sReceiveChannel == channel);
|
||||
|
||||
AdvanceTime(*instance, 19);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == channel);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after window ends");
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestOverlappingReceiveAtSameChannel(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime1;
|
||||
Radio::Time64 startTime2;
|
||||
uint32_t initialRxCallCount;
|
||||
|
||||
printf("TestOverlappingReceiveAtSameChannel()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt`, wait for it to start, then schedule an
|
||||
// overlapping one on the same channel, which ends earlier than
|
||||
// the original one.
|
||||
|
||||
startTime1 = otPlatRadioGetNow(instance) + 50;
|
||||
subMac->ReceiveAt(startTime1, 100, 11);
|
||||
|
||||
AdvanceTime(*instance, 60);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
initialRxCallCount = sReceiveCallCount;
|
||||
|
||||
startTime2 = startTime1 + 20;
|
||||
subMac->ReceiveAt(startTime2, 5, 11);
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
VerifyOrQuit(sReceiveCallCount == initialRxCallCount);
|
||||
|
||||
AdvanceTime(*instance, 4);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
VerifyOrQuit(sReceiveCallCount == initialRxCallCount);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after overlapping window 2 ends");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestOverlappingReceiveAtDifferentChannel(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime1;
|
||||
Radio::Time64 startTime2;
|
||||
|
||||
printf("TestOverlappingReceiveAtDifferentChannel()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt`, wait for it to start, then schedule an
|
||||
// overlapping one on a different channel, which ends earlier than
|
||||
// the original one.
|
||||
|
||||
startTime1 = otPlatRadioGetNow(instance) + 50;
|
||||
subMac->ReceiveAt(startTime1, 100, 11);
|
||||
|
||||
AdvanceTime(*instance, 60);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
startTime2 = startTime1 + 20;
|
||||
subMac->ReceiveAt(startTime2, 6, 15);
|
||||
|
||||
AdvanceTime(*instance, 9);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15, "Radio should switch to channel 15 when window 2 starts");
|
||||
|
||||
AdvanceTime(*instance, 5);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after window 2 ends");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestOverlappingReceiveAtExtendingPastEnd(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime1;
|
||||
Radio::Time64 startTime2;
|
||||
|
||||
printf("TestOverlappingReceiveAtExtendingPastEnd()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt` window 1, wait for it to start, then
|
||||
// schedule an overlapping window 2 on a different channel that
|
||||
// starts before window 1 ends and extends past window 1's end.
|
||||
// Ensure channel switches when window 2 starts and reception
|
||||
// continues until window 2 ends.
|
||||
|
||||
startTime1 = otPlatRadioGetNow(instance) + 50;
|
||||
subMac->ReceiveAt(startTime1, 50, 11);
|
||||
|
||||
AdvanceTime(*instance, 50);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 20);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
startTime2 = startTime1 + 30;
|
||||
subMac->ReceiveAt(startTime2, 60, 15);
|
||||
|
||||
AdvanceTime(*instance, 9);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15, "Radio should switch to channel 15 when window 2 starts");
|
||||
|
||||
AdvanceTime(*instance, 19);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15, "Radio should remain in receive past window 1 end");
|
||||
|
||||
AdvanceTime(*instance, 39);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after window 2 ends");
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestAdjacentBackToBackReceiveAt(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime1;
|
||||
Radio::Time64 startTime2;
|
||||
|
||||
printf("TestAdjacentBackToBackReceiveAt()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt` window 1, then while it is active schedule
|
||||
// a back-to-back adjacent window 2 on a different channel (with zero
|
||||
// gap between windows). Ensure the radio switches channels at the
|
||||
// boundary tick without glitching into sleep, and concludes when
|
||||
// window 2 ends.
|
||||
|
||||
startTime1 = otPlatRadioGetNow(instance) + 50;
|
||||
subMac->ReceiveAt(startTime1, 50, 11);
|
||||
|
||||
AdvanceTime(*instance, 50);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 20);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
startTime2 = startTime1 + 50;
|
||||
subMac->ReceiveAt(startTime2, 40, 15);
|
||||
|
||||
AdvanceTime(*instance, 29);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should switch to window 2 channel at boundary");
|
||||
VerifyOrQuit(sReceiveChannel == 15, "Receive channel mismatch for window 2");
|
||||
|
||||
AdvanceTime(*instance, 39);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after window 2 ends");
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestOverlappingReceiveAtSubsumingWindow(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime1;
|
||||
Radio::Time64 startTime2;
|
||||
|
||||
printf("TestOverlappingReceiveAtSubsumingWindow()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt` window 1, wait for it to start, then
|
||||
// schedule an overlapping window 2 on a different channel that
|
||||
// started earlier and ends later (fully subsuming window 1).
|
||||
// Ensure channel switches immediately to window 2 channel and
|
||||
// continues receiving until window 2 ends.
|
||||
|
||||
startTime1 = otPlatRadioGetNow(instance) + 50;
|
||||
subMac->ReceiveAt(startTime1, 50, 11);
|
||||
|
||||
AdvanceTime(*instance, 50);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 20);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
// Schedule window 2 ([t0 + 40, t0 + 120]) on channel 15 which
|
||||
// started earlier than current time (t0 + 70) and ends later
|
||||
// than window 1 (t0 + 100).
|
||||
startTime2 = startTime1 - 10;
|
||||
subMac->ReceiveAt(startTime2, 80, 15);
|
||||
|
||||
// Radio should immediately switch to window 2 channel
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15, "Radio should switch to window 2 channel immediately");
|
||||
|
||||
AdvanceTime(*instance, 29);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15);
|
||||
|
||||
// Window 1 original end (t0 + 100); verify radio remains receiving on channel 15
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15, "Radio should remain in receive past window 1 end");
|
||||
|
||||
AdvanceTime(*instance, 19);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15);
|
||||
|
||||
// Window 2 ends at t0 + 120; verify transition to sleep
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after window 2 ends");
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestFullyMissedReceiveAt(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime;
|
||||
uint8_t rxChannel;
|
||||
|
||||
printf("TestFullyMissedReceiveAt()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt`, before it can start call `Receive()` on
|
||||
// a different channel blocking timed-rx, wait until after the end
|
||||
// of the scheduled rx to call `Sleep()` to ensure the schedule is
|
||||
// fully missed.
|
||||
|
||||
startTime = otPlatRadioGetNow(instance) + 60;
|
||||
subMac->ReceiveAt(startTime, 40, 11);
|
||||
|
||||
AdvanceTime(*instance, 30);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep);
|
||||
|
||||
rxChannel = 16;
|
||||
SuccessOrQuit(subMac->Receive(rxChannel));
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == rxChannel);
|
||||
|
||||
AdvanceTime(*instance, 70);
|
||||
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == rxChannel);
|
||||
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep since timed window was fully missed");
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep");
|
||||
|
||||
for (uint16_t i = 0; i < 100; i++)
|
||||
{
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep");
|
||||
}
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestInterruptedReceiveAt(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime;
|
||||
uint8_t rxChannel;
|
||||
|
||||
printf("TestInterruptedReceiveAt()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt`, before it can start call `Receive()` on
|
||||
// a different channel blocking timed-rx, then before the scheduled
|
||||
// rx window ends call `Sleep()`. Ensure the timed-rx is started
|
||||
// on its channel after `Sleep()` and then concludes properly when
|
||||
// the window ends.
|
||||
|
||||
startTime = otPlatRadioGetNow(instance) + 60;
|
||||
subMac->ReceiveAt(startTime, 40, 11);
|
||||
|
||||
AdvanceTime(*instance, 50);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep);
|
||||
|
||||
rxChannel = 18;
|
||||
SuccessOrQuit(subMac->Receive(rxChannel));
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == rxChannel);
|
||||
|
||||
AdvanceTime(*instance, 25);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == rxChannel);
|
||||
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should switch to timed-rx channel");
|
||||
VerifyOrQuit(sReceiveChannel == 11, "Receive channel mismatch");
|
||||
|
||||
AdvanceTime(*instance, 24);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after resumed window ends");
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestInterruptedActiveReceiveAt(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime;
|
||||
uint8_t rxChannel;
|
||||
|
||||
printf("TestInterruptedActiveReceiveAt()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt`, wait for it to start, then interrupt it
|
||||
// by calling `Receive()` on a different channel, then before the
|
||||
// scheduled rx window ends call `Sleep()`. Ensure the timed-rx
|
||||
// is resumed on its channel and then concludes properly when the
|
||||
// window ends.
|
||||
|
||||
startTime = otPlatRadioGetNow(instance) + 50;
|
||||
subMac->ReceiveAt(startTime, 50, 11);
|
||||
|
||||
AdvanceTime(*instance, 49);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
rxChannel = 18;
|
||||
SuccessOrQuit(subMac->Receive(rxChannel));
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == rxChannel);
|
||||
|
||||
AdvanceTime(*instance, 15);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == rxChannel);
|
||||
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should switch back to timed-rx channel");
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 24);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after resumed window ends");
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestInterruptedReceiveAtWithSecondSchedule(void)
|
||||
{
|
||||
Instance *instance;
|
||||
Mac::SubMac *subMac;
|
||||
Radio::Time64 startTime1;
|
||||
Radio::Time64 startTime2;
|
||||
uint8_t rxChannel;
|
||||
|
||||
printf("TestInterruptedReceiveAtWithSecondSchedule()\n");
|
||||
ResetPlatformState();
|
||||
|
||||
instance = testInitInstance();
|
||||
subMac = &instance->Get<Mac::SubMac>();
|
||||
|
||||
SuccessOrQuit(subMac->Enable());
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
|
||||
// Schedule a `ReceiveAt` window 1, before it starts block it by
|
||||
// calling `Receive()` on a different channel. Wait until window 1
|
||||
// starts and while blocked in receive state, schedule a future
|
||||
// `ReceiveAt` window 2. Advance time a bit (still within window 1),
|
||||
// then call `Sleep()`. Ensure window 1 is resumed on its channel,
|
||||
// ends properly, and window 2 starts and ends as scheduled.
|
||||
|
||||
startTime1 = otPlatRadioGetNow(instance) + 50;
|
||||
subMac->ReceiveAt(startTime1, 50, 11);
|
||||
|
||||
AdvanceTime(*instance, 30);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep);
|
||||
|
||||
rxChannel = 20;
|
||||
SuccessOrQuit(subMac->Receive(rxChannel));
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == rxChannel);
|
||||
|
||||
AdvanceTime(*instance, 40);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == rxChannel);
|
||||
|
||||
startTime2 = startTime1 + 200;
|
||||
subMac->ReceiveAt(startTime2, 7, 15);
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == rxChannel);
|
||||
|
||||
SuccessOrQuit(subMac->Sleep());
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should switch back to window 1 channel");
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 19);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 11);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after window 1 ends");
|
||||
|
||||
AdvanceTime(*instance, 149);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive, "Radio should enter receive for window 2");
|
||||
VerifyOrQuit(sReceiveChannel == 15);
|
||||
|
||||
AdvanceTime(*instance, 6);
|
||||
VerifyOrQuit(sRadioState == kRadioStateReceive);
|
||||
VerifyOrQuit(sReceiveChannel == 15);
|
||||
|
||||
AdvanceTime(*instance, 1);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should sleep after window 2 ends");
|
||||
|
||||
AdvanceTime(*instance, 10);
|
||||
VerifyOrQuit(sRadioState == kRadioStateSleep, "Radio should remain in sleep");
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
#endif // OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
|
||||
} // namespace ot
|
||||
|
||||
int main(void)
|
||||
{
|
||||
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
|
||||
const uint32_t kLocalRadioTimeDifferences[] = {0, 1000, 1000000};
|
||||
|
||||
for (uint32_t diff : kLocalRadioTimeDifferences)
|
||||
{
|
||||
printf("------------------------------------------------------\n");
|
||||
printf("Setting sLocalRadioTimeDifference: %lu\n", ot::ToUlong(diff));
|
||||
|
||||
ot::sLocalRadioTimeDifference = diff;
|
||||
|
||||
ot::TestSimpleReceiveAt();
|
||||
ot::TestPendingReceiveAtReplaced();
|
||||
ot::TestReceiveAtAlreadyStarted();
|
||||
ot::TestReceiveAtAlreadyExpired();
|
||||
ot::TestReceiveAtWhileActive();
|
||||
ot::TestSleepDuringActiveReceiveAt();
|
||||
ot::TestOverlappingReceiveAtSameChannel();
|
||||
ot::TestOverlappingReceiveAtDifferentChannel();
|
||||
ot::TestOverlappingReceiveAtExtendingPastEnd();
|
||||
ot::TestAdjacentBackToBackReceiveAt();
|
||||
ot::TestOverlappingReceiveAtSubsumingWindow();
|
||||
ot::TestFullyMissedReceiveAt();
|
||||
ot::TestInterruptedReceiveAt();
|
||||
ot::TestInterruptedActiveReceiveAt();
|
||||
ot::TestInterruptedReceiveAtWithSecondSchedule();
|
||||
}
|
||||
|
||||
printf("All tests passed!\n");
|
||||
#else
|
||||
printf("Timed RX feature is not enabled\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user