diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 67b586330..61956cdbf 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -68,6 +68,8 @@ Mle::Mle(Instance &aInstance) , mDeviceMode(ModeTlv::kModeRxOnWhenIdle | ModeTlv::kModeSecureDataRequest) , mAttachState(kAttachStateIdle) , mReattachState(kReattachStop) + , mAttachCounter(0) + , mAnnounceDelay(kAnnounceTimeout) , mAttachTimer(aInstance, &Mle::HandleAttachTimer, this) , mDelayedResponseTimer(aInstance, &Mle::HandleDelayedResponseTimer, this) , mChildUpdateRequestTimer(aInstance, &Mle::HandleChildUpdateRequestTimer, this) @@ -236,6 +238,7 @@ otError Mle::Start(bool aEnableReattach, bool aAnnounceAttach) VerifyOrExit(netif.IsUp(), error = OT_ERROR_INVALID_STATE); SetStateDetached(); + mAttachCounter = 0; netif.GetKeyManager().Start(); @@ -257,7 +260,7 @@ otError Mle::Start(bool aEnableReattach, bool aAnnounceAttach) } else { - mAttachState = kAttachStateSynchronize; + SetAttachState(kAttachStateSynchronize); mChildUpdateAttempts = 0; SendChildUpdateRequest(); } @@ -301,6 +304,16 @@ exit: return; } +void Mle::SetAttachState(AttachState aState) +{ + VerifyOrExit(aState != mAttachState); + otLogInfoMle(GetInstance(), "AttachState %s -> %s", AttachStateToString(mAttachState), AttachStateToString(aState)); + mAttachState = aState; + +exit: + return; +} + otError Mle::Restore(void) { ThreadNetif & netif = GetNetif(); @@ -533,8 +546,6 @@ otError Mle::BecomeChild(AttachMode aMode) VerifyOrExit(mRole != OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE); VerifyOrExit(mAttachState == kAttachStateIdle, error = OT_ERROR_BUSY); - otLogInfoMle(GetInstance(), "Attempt to attach"); - if (mReattachState == kReattachStart) { if (netif.GetActiveDataset().Restore() == OT_ERROR_NONE) @@ -548,9 +559,13 @@ otError Mle::BecomeChild(AttachMode aMode) } ResetParentCandidate(); - mAttachState = kAttachStateStart; + SetAttachState(kAttachStateStart); mParentRequestMode = aMode; + mAttachCounter++; + otLogInfoMle(GetInstance(), "Attempt to attach - attempt %d, %s %s", mAttachCounter, AttachModeToString(aMode), + ReattachStateToString(mReattachState)); + if (aMode != kAttachBetter) { if (mDeviceMode & ModeTlv::kModeFFD) @@ -559,9 +574,7 @@ otError Mle::BecomeChild(AttachMode aMode) } } - netif.GetMeshForwarder().SetRxOnWhenIdle(true); - - mAttachTimer.Start(1 + Random::GetUint32InRange(0, kParentRequestRouterTimeout)); + mAttachTimer.Start(1 + ((mAttachCounter == 1) ? Random::GetUint32InRange(0, kParentRequestRouterTimeout) : 0)); exit: return error; @@ -582,7 +595,7 @@ otError Mle::SetStateDetached(void) } SetRole(OT_DEVICE_ROLE_DETACHED); - mAttachState = kAttachStateIdle; + SetAttachState(kAttachStateIdle); mAttachTimer.Stop(); mChildUpdateRequestTimer.Stop(); netif.GetMeshForwarder().SetRxOnWhenIdle(true); @@ -605,7 +618,8 @@ otError Mle::SetStateChild(uint16_t aRloc16) SetRloc16(aRloc16); SetRole(OT_DEVICE_ROLE_CHILD); - mAttachState = kAttachStateIdle; + SetAttachState(kAttachStateIdle); + mAttachCounter = 0; mReattachState = kReattachStop; mChildUpdateAttempts = 0; netif.GetMac().SetBeaconEnabled(false); @@ -1432,7 +1446,28 @@ void Mle::HandleAttachTimer(Timer &aTimer) void Mle::HandleAttachTimer(void) { - uint32_t delay = 0; + uint32_t delay = 0; + bool shouldAnnounce = true; + + if (mAttachState == kAttachStateParentRequestRouter || mAttachState == kAttachStateParentRequestReed || + mAttachState == kAttachStateAnnounce) + { + // If already attached, accept the parent candidate if + // we are trying to attach to a better partition or if a + // Parent Response was also received from the current parent + // to which the device is attached. This ensures that the + // new parent candidate is compared with the current parent + // and that it is indeed preferred over the current one. + + if (mParentCandidate.GetState() == Neighbor::kStateParentResponse && + (mRole != OT_DEVICE_ROLE_CHILD || mReceivedResponseFromParent || mParentRequestMode == kAttachBetter) && + SendChildIdRequest() == OT_ERROR_NONE) + { + SetAttachState(kAttachStateChildIdRequest); + delay = kParentRequestReedTimeout; + ExitNow(); + } + } switch (mAttachState) { @@ -1445,9 +1480,10 @@ void Mle::HandleAttachTimer(void) break; case kAttachStateStart: - mAttachState = kAttachStateParentRequestRouter; + SetAttachState(kAttachStateParentRequestRouter); mParentCandidate.SetState(Neighbor::kStateInvalid); mReceivedResponseFromParent = false; + GetNetif().GetMeshForwarder().SetRxOnWhenIdle(true); if (mParentRequestMode == kAttachSame1 || mParentRequestMode == kAttachSame2) { @@ -1463,42 +1499,88 @@ void Mle::HandleAttachTimer(void) break; case kAttachStateParentRequestRouter: - mAttachState = kAttachStateParentRequestReed; + SetAttachState(kAttachStateParentRequestReed); + SendParentRequest(kParentRequestTypeRoutersAndReeds); + delay = kParentRequestReedTimeout; + break; - if (mParentCandidate.GetState() != Neighbor::kStateParentResponse) + case kAttachStateParentRequestReed: + shouldAnnounce = PrepareAnnounceState(); + + if (shouldAnnounce) { + SetAttachState(kAttachStateAnnounce); SendParentRequest(kParentRequestTypeRoutersAndReeds); - delay = kParentRequestReedTimeout; + mAnnounceChannel = OT_RADIO_CHANNEL_MIN; + delay = mAnnounceDelay; break; } // fall through - case kAttachStateParentRequestReed: - if (mParentCandidate.GetState() == Neighbor::kStateParentResponse && - (mRole != OT_DEVICE_ROLE_CHILD || mReceivedResponseFromParent || mParentRequestMode == kAttachBetter) && - SendChildIdRequest() == OT_ERROR_NONE) + case kAttachStateAnnounce: + if (shouldAnnounce) { - mAttachState = kAttachStateChildIdRequest; - delay = kParentRequestReedTimeout; - break; + if (SendOrphanAnnounce() == OT_ERROR_NONE) + { + delay = mAnnounceDelay; + break; + } } // fall through case kAttachStateChildIdRequest: - mAttachState = kAttachStateIdle; + SetAttachState(kAttachStateIdle); ResetParentCandidate(); delay = Reattach(); break; } +exit: + if (delay != 0) { mAttachTimer.Start(delay); } } +bool Mle::PrepareAnnounceState(void) +{ + bool shouldAnnounce = false; + uint16_t numChannels = 0; + const MeshCoP::ChannelMask0Tlv *channelMask; + MeshCoP::Dataset dataset(MeshCoP::Tlv::kActiveTimestamp); + + VerifyOrExit((mRole != OT_DEVICE_ROLE_CHILD) && ((mDeviceMode & ModeTlv::kModeFFD) == 0) && + (mReattachState == kReattachStop)); + + SuccessOrExit(GetNetif().GetActiveDataset().Get(dataset)); + channelMask = static_cast(dataset.Get(MeshCoP::Tlv::kChannelMask)); + + VerifyOrExit(channelMask != NULL); + + for (uint8_t channel = OT_RADIO_CHANNEL_MIN; channel <= OT_RADIO_CHANNEL_MAX; channel++) + { + if (channelMask->IsChannelSet(channel)) + { + numChannels++; + } + } + + mAnnounceDelay = kAnnounceTimeout / (numChannels + 1); + + if (mAnnounceDelay < kMinAnnounceDelay) + { + mAnnounceDelay = kMinAnnounceDelay; + } + + shouldAnnounce = true; + +exit: + return shouldAnnounce; +} + uint32_t Mle::Reattach(void) { ThreadNetif &netif = GetNetif(); @@ -1510,8 +1592,8 @@ uint32_t Mle::Reattach(void) { netif.GetPendingDataset().ApplyConfiguration(); mReattachState = kReattachPending; - mAttachState = kAttachStateStart; - delay = kParentRequestRouterTimeout; + SetAttachState(kAttachStateStart); + delay = 1 + Random::GetUint32InRange(0, kParentRequestJitter); } else { @@ -1540,7 +1622,6 @@ uint32_t Mle::Reattach(void) } else if ((mDeviceMode & ModeTlv::kModeFFD) == 0) { - SendOrphanAnnounce(); BecomeDetached(); } else if (netif.GetMle().BecomeLeader() != OT_ERROR_NONE) @@ -2044,45 +2125,30 @@ exit: return error; } -void Mle::SendOrphanAnnounce(void) +otError Mle::SendOrphanAnnounce(void) { + otError error = OT_ERROR_NONE; const MeshCoP::ChannelMask0Tlv *channelMask; - uint8_t channel; MeshCoP::Dataset dataset(MeshCoP::Tlv::kActiveTimestamp); - GetNetif().GetActiveDataset().Get(dataset); + SuccessOrExit(error = GetNetif().GetActiveDataset().Get(dataset)); + channelMask = static_cast(dataset.Get(MeshCoP::Tlv::kChannelMask)); + VerifyOrExit(channelMask != NULL, error = OT_ERROR_NOT_FOUND); - VerifyOrExit(channelMask != NULL); + VerifyOrExit(mAnnounceChannel <= OT_RADIO_CHANNEL_MAX, error = OT_ERROR_NOT_FOUND); - // find next channel in the Active Operational Dataset Channel Mask - channel = mAnnounceChannel; - - while (!channelMask->IsChannelSet(channel)) + while (!channelMask->IsChannelSet(mAnnounceChannel)) { - channel++; - - if (channel > OT_RADIO_CHANNEL_MAX) - { - channel = OT_RADIO_CHANNEL_MIN; - } - - VerifyOrExit(channel != mAnnounceChannel); + mAnnounceChannel++; + VerifyOrExit(mAnnounceChannel <= OT_RADIO_CHANNEL_MAX, error = OT_ERROR_NOT_FOUND); } - // Send Announce message - SendAnnounce(channel, true); - - // Move to next channel - mAnnounceChannel = channel + 1; - - if (mAnnounceChannel > OT_RADIO_CHANNEL_MAX) - { - mAnnounceChannel = OT_RADIO_CHANNEL_MIN; - } + SendAnnounce(mAnnounceChannel, true); + mAnnounceChannel++; exit: - return; + return error; } otError Mle::SendMessage(Message &aMessage, const Ip6::Address &aDestination) @@ -3709,5 +3775,98 @@ const char *Mle::RoleToString(otDeviceRole aRole) return roleString; } +#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_MLE == 1) +const char *Mle::AttachModeToString(AttachMode aMode) +{ + const char *str = "unknown"; + + switch (aMode) + { + case kAttachAny: + str = "any-partition"; + break; + + case kAttachSame1: + str = "same-partition-try-1"; + break; + + case kAttachSame2: + str = "same-partition-try-2"; + break; + + case kAttachBetter: + str = "better-partition"; + break; + } + + return str; +} + +const char *Mle::AttachStateToString(AttachState aState) +{ + const char *str = "Unknown"; + + switch (aState) + { + case kAttachStateIdle: + str = "Idle"; + break; + + case kAttachStateSynchronize: + str = "Sync"; + break; + + case kAttachStateStart: + str = "Start"; + break; + + case kAttachStateParentRequestRouter: + str = "ParentReqRouters"; + break; + + case kAttachStateParentRequestReed: + str = "ParentReqReeds"; + break; + + case kAttachStateAnnounce: + str = "Announce"; + break; + + case kAttachStateChildIdRequest: + str = "ChildIdReq"; + break; + }; + + return str; +} + +const char *Mle::ReattachStateToString(ReattachState aState) +{ + const char *str = "unknown"; + + switch (aState) + { + case kReattachStop: + str = ""; + break; + + case kReattachStart: + str = "reattaching"; + break; + + case kReattachActive: + str = "reattaching with Active Dataset"; + break; + + case kReattachPending: + str = "reattaching with Pending Dataset"; + break; + } + + return str; +} + +#endif // (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_MLE == 1) + } // namespace Mle } // namespace ot diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index b4ae1ada4..cc36a1f13 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -958,6 +958,33 @@ public: static const char *RoleToString(otDeviceRole aRole); protected: + /** + * States during attach (when searching for a parent). + * + */ + enum AttachState + { + kAttachStateIdle, ///< Not currently searching for a parent. + kAttachStateSynchronize, ///< Looking to synchronize with a parent (after reset). + kAttachStateStart, ///< Starting to look for a parent. + kAttachStateParentRequestRouter, ///< Searching for a Router to attach to. + kAttachStateParentRequestReed, ///< Searching for Routers or REEDs to attach to. + kAttachStateAnnounce, ///< Send Announce messages + kAttachStateChildIdRequest, ///< Sending a Child ID Request message. + }; + + /** + * States when reattaching network using stored dataset + * + */ + enum ReattachState + { + kReattachStop = 0, ///< Reattach process is disabled or finished + kReattachStart = 1, ///< Start reattach process + kReattachActive = 2, ///< Reattach using stored Active Dataset + kReattachPending = 3, ///< Reattach using stored Pending Dataset + }; + enum { kMleMaxResponseDelay = 1000u, ///< Maximum delay before responding to a multicast request. @@ -979,6 +1006,14 @@ protected: */ void SetRole(otDeviceRole aRole); + /** + * This method sets the attach state + * + * @param[in] aState An attach state + * + */ + void SetAttachState(AttachState aState); + /** * This method appends an MLE header to a message. * @@ -1398,46 +1433,54 @@ protected: */ void InformPreviousChannel(void); - LeaderDataTlv mLeaderData; ///< Last received Leader Data TLV. - bool mRetrieveNewNetworkData; ///< Indicating new Network Data is needed if set. - - otDeviceRole mRole; ///< Current Thread role. - Router mParent; ///< Parent information. - uint8_t mDeviceMode; ///< Device mode setting. - +#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_MLE == 1) /** - * States during attach (when searching for a parent). + * This method converts an `AttachMode` enumeration value into a human-readable string. + * + * @param[in] aMode An attach mode + * + * @returns A human-readable string corresponding to the attach mode. * */ - enum AttachState - { - kAttachStateIdle, ///< Not currently searching for a parent. - kAttachStateSynchronize, ///< Looking to synchronize with a parent (after reset). - kAttachStateStart, ///< Starting to look for a parent. - kAttachStateParentRequestRouter, ///< Searching for a Router to attach to. - kAttachStateParentRequestReed, ///< Searching for Routers or REEDs to attach to. - kAttachStateChildIdRequest, ///< Sending a Child ID Request message. - }; - AttachState mAttachState; ///< The parent request state. + static const char *AttachModeToString(AttachMode aMode); /** - * States when reattaching network using stored dataset + * This method converts an `AttachState` enumeration value into a human-readable string. + * + * @param[in] aState An attach state + * + * @returns A human-readable string corresponding to the attach state. * */ - enum ReattachState - { - kReattachStop = 0, ///< Reattach process is disabled or finished - kReattachStart = 1, ///< Start reattach process - kReattachActive = 2, ///< Reattach using stored Active Dataset - kReattachPending = 3, ///< Reattach using stored Pending Dataset - }; - ReattachState mReattachState; + static const char *AttachStateToString(AttachState aState); - TimerMilli mAttachTimer; ///< The timer for driving the attach process. - TimerMilli mDelayedResponseTimer; ///< The timer to delay MLE responses. - TimerMilli mChildUpdateRequestTimer; ///< The timer for sending MLE Child Update Request messages. + /** + * This method converts a `ReattachState` enumeration value into a human-readable string. + * + * @param[in] aState A reattach state + * + * @returns A human-readable string corresponding to the reattach state. + * + */ + static const char *ReattachStateToString(ReattachState aState); +#endif - uint8_t mParentLeaderCost; + LeaderDataTlv mLeaderData; ///< Last received Leader Data TLV. + bool mRetrieveNewNetworkData; ///< Indicating new Network Data is needed if set. + otDeviceRole mRole; ///< Current Thread role. + Router mParent; ///< Parent information. + uint8_t mDeviceMode; ///< Device mode setting. + AttachState mAttachState; ///< The parent request state. + ReattachState mReattachState; ///< Reattach state + uint16_t mAttachCounter; ///< Attach attempt counter. + uint16_t mAnnounceDelay; ///< Delay in between sending Announce messages during attach. + TimerMilli mAttachTimer; ///< The timer for driving the attach process. + TimerMilli mDelayedResponseTimer; ///< The timer to delay MLE responses. + TimerMilli mChildUpdateRequestTimer; ///< The timer for sending MLE Child Update Request messages. + uint32_t mLastPartitionId; ///< The partition ID of the previous Thread partition + uint8_t mLastPartitionRouterIdSequence; ///< The router ID sequence from the previous Thread partition + uint8_t mLastPartitionIdTimeout; ///< The time remaining to avoid the previous Thread partition + uint8_t mParentLeaderCost; private: enum @@ -1493,7 +1536,8 @@ private: otError SendParentRequest(ParentRequestType aType); otError SendChildIdRequest(void); - void SendOrphanAnnounce(void); + otError SendOrphanAnnounce(void); + bool PrepareAnnounceState(void); otError SendAnnounce(uint8_t aChannel, bool aOrphanAnnounce, const Ip6::Address &aDestination); uint32_t Reattach(void); diff --git a/src/core/thread/mle_constants.hpp b/src/core/thread/mle_constants.hpp index f8453aa67..05b06e801 100644 --- a/src/core/thread/mle_constants.hpp +++ b/src/core/thread/mle_constants.hpp @@ -61,6 +61,9 @@ enum kUdpPort = 19788, ///< MLE UDP Port kParentRequestRouterTimeout = 750, ///< Router Parent Request timeout kParentRequestReedTimeout = 1250, ///< Router and REEDs Parent Request timeout + kParentRequestJitter = 50, ///< Maximum jitter time added to Parent Request timeout + kAnnounceTimeout = 1400, ///< Total timeout used for sending Announcement messages + kMinAnnounceDelay = 80, ///< Minimum delay between Announcement messages kParentResponseMaxDelayRouters = 500, ///< Maximum delay for response for Parent Request sent to routers only kParentResponseMaxDelayAll = 1000, ///< Maximum delay for response for Parent Request sent to all devices kUnicastRetransmissionDelay = 1000, ///< Base delay before retransmitting an MLE unicast. diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index d2eec4eee..1a8192b44 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -296,7 +296,8 @@ otError MleRouter::SetStateRouter(uint16_t aRloc16) SetRloc16(aRloc16); SetRole(OT_DEVICE_ROLE_ROUTER); - mAttachState = kAttachStateIdle; + SetAttachState(kAttachStateIdle); + mAttachCounter = 0; mAttachTimer.Stop(); mChildUpdateRequestTimer.Stop(); mAdvertiseTimer.Stop(); @@ -330,7 +331,8 @@ otError MleRouter::SetStateLeader(uint16_t aRloc16) SetRloc16(aRloc16); SetRole(OT_DEVICE_ROLE_LEADER); - mAttachState = kAttachStateIdle; + SetAttachState(kAttachStateIdle); + mAttachCounter = 0; mAttachTimer.Stop(); mChildUpdateRequestTimer.Stop(); mAdvertiseTimer.Stop(); diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 32b329481..d7fc35da9 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -127,5 +127,6 @@ run test-100-mcu-power-state.py run test-600-channel-manager-properties.py run test-601-channel-manager-channel-change.py run test-602-channel-manager-channel-select.py +run test-603-channel-manager-announce-recovery.py exit 0 diff --git a/tests/toranj/test-603-channel-manager-announce-recovery.py b/tests/toranj/test-603-channel-manager-announce-recovery.py new file mode 100644 index 000000000..675a1eb7e --- /dev/null +++ b/tests/toranj/test-603-channel-manager-announce-recovery.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# +# Copyright (c) 2018, 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. + +from wpan import verify +import wpan +import time + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: Orphaned node attach through MLE Announcement + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + +def verify_channel(nodes, new_channel, wait_time=20): + """ + This function checks the channel on a given list of `nodes` and verifies that all nodes + switch to a given `new_channel` (as int) within certain `wait_time` (int and in seconds) + """ + start_time = time.time() + + while not all([ (new_channel == int(node.get(wpan.WPAN_CHANNEL), 0)) for node in nodes ]): + if time.time() - start_time > wait_time: + print 'Took too long to switch to channel {} ({}>{} sec)'.format(new_channel, time.time() - start_time, + wait_time) + exit(1) + time.sleep(0.1) + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +router = wpan.Node() +c1 = wpan.Node() +c2 = wpan.Node() + +all_nodes = [router, c1, c2] + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +#----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +for node in all_nodes: + node.set(wpan.WPAN_OT_LOG_LEVEL, '0') + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +router.form('announce-tst', channel=11) + +c1.join_node(router, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE) +c2.join_node(router, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE) + +c1.set(wpan.WPAN_POLL_INTERVAL, '500') +c2.set(wpan.WPAN_POLL_INTERVAL, '500') + +c1.set(wpan.WPAN_THREAD_DEVICE_MODE,'5') +c2.set(wpan.WPAN_THREAD_DEVICE_MODE,'5') + +# Reset c2 and keep it in detached state +c2.set('Daemon:AutoAssociateAfterReset', 'false') +c2.reset(); + +# Switch the rest of network to channel 26 +router.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '26') +verify_channel([router, c1], 26) + +# Now re-enable c2 and verify that it does attach to router and is on channel 26 +# c2 would go through the ML Announce recovery. + +c2.set('Daemon:AutoAssociateAfterReset', 'true') +c2.reset(); +verify(int(c2.get(wpan.WPAN_CHANNEL), 0) == 11) + +# wait for 20s for c2 to be attached/associated +start_time = time.time() +wait_time = 20 + +while not c2.is_associated(): + if time.time() - start_time > wait_time: + print 'Took too long to recover through ML Announce ({}>{} sec)'.format(time.time() - start_time, wait_time) + exit(1) + time.sleep(0.1) + +# Check that c2 did attach and is on channel 26. +verify(int(c2.get(wpan.WPAN_CHANNEL), 0) == 26) + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +print '\'{}\' passed.'.format(test_name) +