diff --git a/include/commissioning/commissioner.h b/include/commissioning/commissioner.h index 9a89a2eec..5ae900121 100644 --- a/include/commissioning/commissioner.h +++ b/include/commissioning/commissioner.h @@ -66,9 +66,63 @@ ThreadError otCommissionerStart(otInstance *aInstance, const char *aPSKd); */ ThreadError otCommissionerStop(otInstance *aInstance); +/** + * This function pointer is called when the Commissioner receives an Energy Report. + * + * @param[in] aChannelMask The channel mask value. + * @param[in] aEnergyList A pointer to the energy measurement list. + * @param[in] aEnergyListLength Number of entries in @p aEnergyListLength. + * @param[in] aContext A pointer to application-specific context. + * + */ +typedef void (*otCommissionerEnergyReportCallback)(uint32_t aChannelMask, const uint8_t *aEnergyList, + uint8_t aEnergyListLength, void *aContext); + +/** + * This function sends an Energy Scan Query message. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aChannelMask The channel mask value. + * @param[in] aCount The number of energy measurements per channel. + * @param[in] aPeriod The time between energy measurements (milliseconds). + * @param[in] aScanDuration The scan duration for each energy measurement (milliseconds). + * @param[in] aAddress A pointer to the IPv6 destination. + * @param[in] aCallback A pointer to a function called on receiving an Energy Report message. + * @param[in] aContext A pointer to application-specific context. + * + * @retval kThreadError_None Successfully enqueued the Energy Scan Query message. + * @retval kThreadError_NoBufs Insufficient buffers to generate an Energy Scan Query message. + * + */ +ThreadError otCommissionerEnergyScan(otInstance *aInstance, uint32_t aChannelMask, uint8_t aCount, uint16_t aPeriod, + uint16_t aScanDuration, const otIp6Address *aAddress, + otCommissionerEnergyReportCallback aCallback, void *aContext); + +/** + * This function pointer is called when the Commissioner receives a PAN ID Conflict message. + * + * @param[in] aPanId The PAN ID value. + * @param[in] aChannelMask The channel mask value. + * @param[in] aContext A pointer to application-specific context. + * + */ typedef void (*otCommissionerPanIdConflictCallback)(uint16_t aPanId, uint32_t aChannelMask, void *aContext); -ThreadError otCommissionerPanIdQuery(otInstance *, uint16_t aPanId, uint32_t aChannelMask, +/** + * This function sends a PAN ID Query message. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aPanId The PAN ID to query. + * @param[in] aChannelMask The channel mask value. + * @param[in] aAddress A pointer to the IPv6 destination. + * @param[in] aCallback A pointer to a function called on receiving an Energy Report message. + * @param[in] aContext A pointer to application-specific context. + * + * @retval kThreadError_None Successfully enqueued the PAN ID Query message. + * @retval kThreadError_NoBufs Insufficient buffers to generate a PAN ID Query message. + * + */ +ThreadError otCommissionerPanIdQuery(otInstance *aInstance, uint16_t aPanId, uint32_t aChannelMask, const otIp6Address *aAddress, otCommissionerPanIdConflictCallback aCallback, void *aContext); diff --git a/include/openthread-types.h b/include/openthread-types.h index cf5a0a04e..8e4746295 100644 --- a/include/openthread-types.h +++ b/include/openthread-types.h @@ -401,6 +401,10 @@ typedef enum otMeshcopTlvType OT_MESHCOP_TLV_PENDINGTIMESTAMP = 51, ///< meshcop Pending Timestamp TLV OT_MESHCOP_TLV_DELAYTIMER = 52, ///< meshcop Delay Timer TLV OT_MESHCOP_TLV_CHANNELMASK = 53, ///< meshcop Channel Mask TLV + OT_MESHCOP_TLV_COUNT = 54, ///< meshcop Count TLV + OT_MESHCOP_TLV_PERIOD = 55, ///< meshcop Period TLV + OT_MESHCOP_TLV_SCAN_DURATION = 56, ///< meshcop Scan Duration TLV + OT_MESHCOP_TLV_ENERGY_LIST = 57, ///< meshcop Energy List TLV OT_MESHCOP_TLV_DISCOVERYREQUEST = 128, ///< meshcop Discovery Request TLV OT_MESHCOP_TLV_DISCOVERYRESPONSE = 129, ///< meshcop Discovery Response TLV } otMeshcopTlvType; diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 81fcf8ecd..201334fc2 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -1919,6 +1919,40 @@ void Interpreter::ProcessCommissioner(int argc, char *argv[]) { otCommissionerStop(mInstance); } + else if (strcmp(argv[0], "energy") == 0) + { + long mask; + long count; + long period; + long scanDuration; + otIp6Address address; + + VerifyOrExit(argc > 5, error = kThreadError_Parse); + + // mask + SuccessOrExit(error = ParseLong(argv[1], mask)); + + // count + SuccessOrExit(error = ParseLong(argv[2], count)); + + // period + SuccessOrExit(error = ParseLong(argv[3], period)); + + // scan duration + SuccessOrExit(error = ParseLong(argv[4], scanDuration)); + + // destination + SuccessOrExit(error = otIp6AddressFromString(argv[5], &address)); + + SuccessOrExit(error = otCommissionerEnergyScan(mInstance, + static_cast(mask), + static_cast(count), + static_cast(period), + static_cast(scanDuration), + &address, + Interpreter::s_HandleEnergyReport, + this)); + } else if (strcmp(argv[0], "panid") == 0) { long panid; @@ -1936,15 +1970,36 @@ void Interpreter::ProcessCommissioner(int argc, char *argv[]) // destination SuccessOrExit(error = otIp6AddressFromString(argv[3], &address)); - SuccessOrExit(error = otCommissionerPanIdQuery(mInstance, static_cast(panid), + SuccessOrExit(error = otCommissionerPanIdQuery(mInstance, + static_cast(panid), static_cast(mask), - &address, Interpreter::s_HandlePanIdConflict, this)); + &address, + Interpreter::s_HandlePanIdConflict, + this)); } exit: AppendResult(error); } +void Interpreter::s_HandleEnergyReport(uint32_t aChannelMask, const uint8_t *aEnergyList, uint8_t aEnergyListLength, + void *aContext) +{ + static_cast(aContext)->HandleEnergyReport(aChannelMask, aEnergyList, aEnergyListLength); +} + +void Interpreter::HandleEnergyReport(uint32_t aChannelMask, const uint8_t *aEnergyList, uint8_t aEnergyListLength) +{ + sServer->OutputFormat("Energy: %08x ", aChannelMask); + + for (uint8_t i = 0; i < aEnergyListLength; i++) + { + sServer->OutputFormat("%d ", aEnergyList[i]); + } + + sServer->OutputFormat("\r\n"); +} + void Interpreter::s_HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask, void *aContext) { static_cast(aContext)->HandlePanIdConflict(aPanId, aChannelMask); diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 679770632..b706ddb44 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -208,6 +208,8 @@ private: static void s_HandleActiveScanResult(otActiveScanResult *aResult, void *aContext); static void s_HandleNetifStateChanged(uint32_t aFlags, void *aContext); static void s_HandleLinkPcapReceive(const RadioPacket *aFrame, void *aContext); + static void s_HandleEnergyReport(uint32_t aChannelMask, const uint8_t *aEnergyList, uint8_t aEnergyListLength, + void *aContext); static void s_HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask, void *aContext); void HandleEchoResponse(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); @@ -215,6 +217,7 @@ private: void HandleActiveScanResult(otActiveScanResult *aResult); void HandleNetifStateChanged(uint32_t aFlags); void HandleLinkPcapReceive(const RadioPacket *aFrame); + void HandleEnergyReport(uint32_t aChannelMask, const uint8_t *aEnergyList, uint8_t aEnergyListLength); void HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask); static const struct Command sCommands[]; diff --git a/src/core/Makefile.am b/src/core/Makefile.am index cd6135bd4..18c12823d 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -63,6 +63,7 @@ libopenthread_a_SOURCES = \ net/netif.cpp \ net/udp6.cpp \ thread/address_resolver.cpp \ + thread/energy_scan_server.cpp \ thread/key_manager.cpp \ thread/link_quality.cpp \ thread/lowpan.cpp \ @@ -84,6 +85,7 @@ libopenthread_a_SOURCES = \ if OPENTHREAD_ENABLE_COMMISSIONER libopenthread_a_SOURCES += \ meshcop/commissioner.cpp \ + meshcop/energy_scan_client.cpp \ meshcop/panid_query_client.cpp \ $(NULL) endif # OPENTHREAD_ENABLE_COMMISSIONER @@ -124,6 +126,7 @@ noinst_HEADERS = \ mac/mac_blacklist.hpp \ meshcop/commissioner.hpp \ meshcop/dtls.hpp \ + meshcop/energy_scan_client.hpp \ meshcop/joiner.hpp \ meshcop/joiner_router.hpp \ meshcop/leader.hpp \ @@ -140,6 +143,7 @@ noinst_HEADERS = \ net/udp6.hpp \ net/tcp.hpp \ thread/address_resolver.hpp \ + thread/energy_scan_server.hpp \ thread/key_manager.hpp \ thread/link_quality.hpp \ thread/lowpan.hpp \ diff --git a/src/core/meshcop/commissioner.cpp b/src/core/meshcop/commissioner.cpp index 01229bd4b..87dad3092 100644 --- a/src/core/meshcop/commissioner.cpp +++ b/src/core/meshcop/commissioner.cpp @@ -53,6 +53,7 @@ namespace Thread { namespace MeshCoP { Commissioner::Commissioner(ThreadNetif &aThreadNetif): + mEnergyScan(aThreadNetif), mPanIdQuery(aThreadNetif), mTimer(aThreadNetif.GetIp6().mTimerScheduler, HandleTimer, this), mTransmitTask(aThreadNetif.GetIp6().mTaskletScheduler, &HandleUdpTransmit, this), diff --git a/src/core/meshcop/commissioner.hpp b/src/core/meshcop/commissioner.hpp index 9c742795d..60f6e75f3 100644 --- a/src/core/meshcop/commissioner.hpp +++ b/src/core/meshcop/commissioner.hpp @@ -36,6 +36,7 @@ #include #include +#include #include #include #include @@ -83,6 +84,7 @@ public: */ uint16_t GetSessionId(void) const; + EnergyScanClient mEnergyScan; PanIdQueryClient mPanIdQuery; private: diff --git a/src/core/meshcop/energy_scan_client.cpp b/src/core/meshcop/energy_scan_client.cpp new file mode 100644 index 000000000..8dad6442e --- /dev/null +++ b/src/core/meshcop/energy_scan_client.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2016, 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. + */ + +/** + * @file + * This file implements the Energy Scan Client. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using Thread::Encoding::BigEndian::HostSwap16; +using Thread::Encoding::BigEndian::HostSwap32; + +namespace Thread { + +EnergyScanClient::EnergyScanClient(ThreadNetif &aThreadNetif) : + mEnergyScan(OPENTHREAD_URI_ENERGY_REPORT, &HandleReport, this), + mSocket(aThreadNetif.GetIp6().mUdp), + mTimer(aThreadNetif.GetIp6().mTimerScheduler, &HandleTimer, this), + mCoapServer(aThreadNetif.GetCoapServer()), + mNetif(aThreadNetif) +{ + mCoapServer.AddResource(mEnergyScan); + mSocket.Open(HandleUdpReceive, this); +} + +ThreadError EnergyScanClient::SendQuery(uint32_t aChannelMask, uint8_t aCount, uint16_t aPeriod, + uint16_t aScanDuration, const Ip6::Address &aAddress, + otCommissionerEnergyReportCallback aCallback, void *aContext) +{ + ThreadError error = kThreadError_None; + Coap::Header header; + MeshCoP::CommissionerSessionIdTlv sessionId; + MeshCoP::ChannelMaskTlv channelMask; + union + { + MeshCoP::ChannelMaskEntry channelMaskEntry; + uint8_t channelMaskBuf[sizeof(MeshCoP::ChannelMaskEntry) + sizeof(aChannelMask)]; + }; + MeshCoP::CountTlv count; + MeshCoP::PeriodTlv period; + MeshCoP::ScanDurationTlv scanDuration; + Ip6::MessageInfo messageInfo; + Message *message; + + header.Init(); + header.SetVersion(1); + header.SetType(aAddress.IsMulticast() ? Coap::Header::kTypeNonConfirmable : Coap::Header::kTypeConfirmable); + header.SetCode(Coap::Header::kCodePost); + header.SetMessageId(0); + header.SetToken(NULL, 0); + header.AppendUriPathOptions(OPENTHREAD_URI_ENERGY_SCAN); + header.AppendContentFormatOption(Coap::Header::kApplicationOctetStream); + header.Finalize(); + + VerifyOrExit((message = mSocket.NewMessage(0)) != NULL, error = kThreadError_NoBufs); + SuccessOrExit(error = message->Append(header.GetBytes(), header.GetLength())); + + sessionId.Init(); + sessionId.SetCommissionerSessionId(mNetif.GetCommissioner().GetSessionId()); + SuccessOrExit(error = message->Append(&sessionId, sizeof(sessionId))); + + channelMask.Init(); + channelMask.SetLength(sizeof(channelMaskBuf)); + SuccessOrExit(error = message->Append(&channelMask, sizeof(channelMask))); + + channelMaskEntry.SetChannelPage(0); + channelMaskEntry.SetMaskLength(sizeof(aChannelMask)); + + for (size_t i = 0; i < sizeof(aChannelMask); i++) + { + channelMaskBuf[sizeof(MeshCoP::ChannelMaskEntry) + i] = (aChannelMask >> (8 * i)) & 0xff; + } + + SuccessOrExit(error = message->Append(channelMaskBuf, sizeof(channelMaskBuf))); + + count.Init(); + count.SetCount(aCount); + SuccessOrExit(error = message->Append(&count, sizeof(count))); + + period.Init(); + period.SetPeriod(aPeriod); + SuccessOrExit(error = message->Append(&period, sizeof(period))); + + scanDuration.Init(); + scanDuration.SetScanDuration(aScanDuration); + SuccessOrExit(error = message->Append(&scanDuration, sizeof(scanDuration))); + + memset(&messageInfo, 0, sizeof(messageInfo)); + messageInfo.GetPeerAddr() = aAddress; + messageInfo.mPeerPort = kCoapUdpPort; + messageInfo.mInterfaceId = mNetif.GetInterfaceId(); + SuccessOrExit(error = mSocket.SendTo(*message, messageInfo)); + + otLogInfoMeshCoP("sent energy scan query\r\n"); + + mCallback = aCallback; + mContext = aContext; + +exit: + + if (error != kThreadError_None && message != NULL) + { + message->Free(); + } + + return error; +} + +void EnergyScanClient::HandleReport(void *aContext, Coap::Header &aHeader, Message &aMessage, + const Ip6::MessageInfo &aMessageInfo) +{ + static_cast(aContext)->HandleReport(aHeader, aMessage, aMessageInfo); +} + +void EnergyScanClient::HandleReport(Coap::Header &aHeader, Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + OT_TOOL_PACKED_BEGIN + struct + { + MeshCoP::ChannelMaskTlv tlv; + MeshCoP::ChannelMaskEntry header; + uint32_t mask; + } OT_TOOL_PACKED_END channelMask; + + OT_TOOL_PACKED_BEGIN + struct + { + MeshCoP::EnergyListTlv tlv; + uint8_t list[OPENTHREAD_CONFIG_MAX_ENERGY_RESULTS]; + } OT_TOOL_PACKED_END energyList; + + VerifyOrExit(aHeader.GetType() == Coap::Header::kTypeConfirmable && + aHeader.GetCode() == Coap::Header::kCodePost, ;); + + otLogInfoMeshCoP("received energy scan report\r\n"); + + SuccessOrExit(MeshCoP::Tlv::GetTlv(aMessage, MeshCoP::Tlv::kChannelMask, sizeof(channelMask), channelMask.tlv)); + VerifyOrExit(channelMask.tlv.IsValid() && + channelMask.header.GetChannelPage() == 0 && + channelMask.header.GetMaskLength() >= sizeof(uint32_t), ;); + + SuccessOrExit(MeshCoP::Tlv::GetTlv(aMessage, MeshCoP::Tlv::kEnergyList, sizeof(energyList), energyList.tlv)); + VerifyOrExit(energyList.tlv.IsValid(), ;); + + if (mCallback != NULL) + { + mCallback(HostSwap32(channelMask.mask), energyList.list, energyList.tlv.GetLength(), mContext); + } + + SendResponse(aHeader, aMessageInfo); + +exit: + return; +} + +ThreadError EnergyScanClient::SendResponse(const Coap::Header &aRequestHeader, const Ip6::MessageInfo &aRequestInfo) +{ + ThreadError error = kThreadError_None; + Message *message; + Coap::Header responseHeader; + Ip6::MessageInfo responseInfo; + + VerifyOrExit((message = mCoapServer.NewMessage(0)) != NULL, error = kThreadError_NoBufs); + + responseHeader.Init(); + responseHeader.SetVersion(1); + responseHeader.SetType(Coap::Header::kTypeAcknowledgment); + responseHeader.SetCode(Coap::Header::kCodeChanged); + responseHeader.SetMessageId(aRequestHeader.GetMessageId()); + responseHeader.SetToken(aRequestHeader.GetToken(), aRequestHeader.GetTokenLength()); + responseHeader.Finalize(); + SuccessOrExit(error = message->Append(responseHeader.GetBytes(), responseHeader.GetLength())); + + memcpy(&responseInfo, &aRequestInfo, sizeof(responseInfo)); + memset(&responseInfo.mSockAddr, 0, sizeof(responseInfo.mSockAddr)); + SuccessOrExit(error = mCoapServer.SendMessage(*message, responseInfo)); + +exit: + + if (error != kThreadError_None && message != NULL) + { + message->Free(); + } + + return error; +} + +void EnergyScanClient::HandleTimer(void *aContext) +{ + static_cast(aContext)->HandleTimer(); +} + +void EnergyScanClient::HandleTimer(void) +{ +} + +void EnergyScanClient::HandleUdpReceive(void *aContext, otMessage aMessage, const otMessageInfo *aMessageInfo) +{ + otLogInfoMeshCoP("received energy scan query response\r\n"); + (void)aContext; + (void)aMessage; + (void)aMessageInfo; +} + +} // namespace Thread diff --git a/src/core/meshcop/energy_scan_client.hpp b/src/core/meshcop/energy_scan_client.hpp new file mode 100644 index 000000000..ca144d7d7 --- /dev/null +++ b/src/core/meshcop/energy_scan_client.hpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2016, 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. + */ + +/** + * @file + * This file includes definitions for responding to PANID Query Requests. + */ + +#ifndef ENERGY_SCAN_CLIENT_HPP_ +#define ENERGY_SCAN_CLIENT_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +namespace Thread { + +class ThreadNetif; + +/** + * This class implements handling PANID Query Requests. + * + */ +class EnergyScanClient +{ +public: + /** + * This constructor initializes the object. + * + */ + EnergyScanClient(ThreadNetif &aThreadNetif); + + /** + * This method sends an Energy Scan Query message. + * + * @param[in] aChannelMask The channel mask value. + * @param[in] aCount The number of energy measurements per channel. + * @param[in] aPeriod The time between energy measurements (milliseconds). + * @param[in] aScanDuration The scan duration for each energy measurement (milliseconds). + * @param[in] aAddress A pointer to the IPv6 destination. + * @param[in] aCallback A pointer to a function called on receiving an Energy Report message. + * @param[in] aContext A pointer to application-specific context. + * + * @retval kThreadError_None Successfully enqueued the Energy Scan Query message. + * @retval kThreadError_NoBufs Insufficient buffers to generate an Energy Scan Query message. + * + */ + ThreadError SendQuery(uint32_t aChannelMask, uint8_t aCount, uint16_t aPeriod, uint16_t aScanDuration, + const Ip6::Address &aAddress, otCommissionerEnergyReportCallback aCallback, void *aContext); + +private: + static void HandleReport(void *aContext, Coap::Header &aHeader, Message &aMessage, + const Ip6::MessageInfo &aMessageInfo); + void HandleReport(Coap::Header &aHeader, Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + + static void HandleTimer(void *aContext); + void HandleTimer(void); + + static void HandleUdpReceive(void *aContext, otMessage aMessage, const otMessageInfo *aMessageInfo); + + ThreadError SendResponse(const Coap::Header &aRequestHeader, const Ip6::MessageInfo &aRequestMessageInfo); + + otCommissionerEnergyReportCallback mCallback; + void *mContext; + + Coap::Resource mEnergyScan; + Ip6::UdpSocket mSocket; + Timer mTimer; + + Coap::Server &mCoapServer; + ThreadNetif &mNetif; +}; + +/** + * @} + */ + +} // namespace Thread + +#endif // ENERGY_SCAN_CLIENT_HPP_ diff --git a/src/core/meshcop/panid_query_client.hpp b/src/core/meshcop/panid_query_client.hpp index 04be55860..016307ee6 100644 --- a/src/core/meshcop/panid_query_client.hpp +++ b/src/core/meshcop/panid_query_client.hpp @@ -59,6 +59,19 @@ public: */ PanIdQueryClient(ThreadNetif &aThreadNetif); + /** + * This method sends a PAN ID Query message. + * + * @param[in] aPanId The PAN ID to query. + * @param[in] aChannelMask The channel mask value. + * @param[in] aAddress A pointer to the IPv6 destination. + * @param[in] aCallback A pointer to a function called on receiving an Energy Report message. + * @param[in] aContext A pointer to application-specific context. + * + * @retval kThreadError_None Successfully enqueued the PAN ID Query message. + * @retval kThreadError_NoBufs Insufficient buffers to generate a PAN ID Query message. + * + */ ThreadError SendQuery(uint16_t aPanId, uint32_t aChannelMask, const Ip6::Address &aAddress, otCommissionerPanIdConflictCallback aCallback, void *aContext); diff --git a/src/core/openthread-core-default-config.h b/src/core/openthread-core-default-config.h index 003eec36c..c2c77f278 100644 --- a/src/core/openthread-core-default-config.h +++ b/src/core/openthread-core-default-config.h @@ -165,6 +165,16 @@ #define OPENTHREAD_CONFIG_JOINER_UDP_PORT 1000 #endif // OPENTHREAD_CONFIG_JOINER_UDP_PORT +/** + * @def OPENTHREAD_CONFIG_MAX_ENERGY_RESULTS + * + * The maximum number of Energy List entries. + * + */ +#ifndef OPENTHREAD_CONFIG_MAX_ENERGY_RESULTS +#define OPENTHREAD_CONFIG_MAX_ENERGY_RESULTS 64 +#endif // OPENTHREAD_CONFIG_MAX_ENERGY_RESULTS + /** * @def OPENTHREAD_CONFIG_LOG_LEVEL * diff --git a/src/core/openthread.cpp b/src/core/openthread.cpp index c805e502b..10b489c61 100644 --- a/src/core/openthread.cpp +++ b/src/core/openthread.cpp @@ -1325,6 +1325,15 @@ ThreadError otCommissionerStop(otInstance *) return sThreadNetif->GetCommissioner().Stop(); } +ThreadError otCommissionerEnergyScan(otInstance *, uint32_t aChannelMask, uint8_t aCount, uint16_t aPeriod, + uint16_t aScanDuration, const otIp6Address *aAddress, + otCommissionerEnergyReportCallback aCallback, void *aContext) +{ + return sThreadNetif->GetCommissioner().mEnergyScan.SendQuery(aChannelMask, aCount, aPeriod, aScanDuration, + *static_cast(aAddress), + aCallback, aContext); +} + ThreadError otCommissionerPanIdQuery(otInstance *, uint16_t aPanId, uint32_t aChannelMask, const otIp6Address *aAddress, otCommissionerPanIdConflictCallback aCallback, void *aContext) diff --git a/src/core/thread/energy_scan_server.cpp b/src/core/thread/energy_scan_server.cpp new file mode 100644 index 000000000..5c9ecd8af --- /dev/null +++ b/src/core/thread/energy_scan_server.cpp @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2016, 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. + */ + +/** + * @file + * This file implements the Energy Scan Server. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Thread { + +EnergyScanServer::EnergyScanServer(ThreadNetif &aThreadNetif) : + mActive(false), + mEnergyScan(OPENTHREAD_URI_ENERGY_SCAN, &HandleRequest, this), + mSocket(aThreadNetif.GetIp6().mUdp), + mTimer(aThreadNetif.GetIp6().mTimerScheduler, &HandleTimer, this), + mCoapServer(aThreadNetif.GetCoapServer()), + mNetif(aThreadNetif) +{ + mNetifCallback.Set(&HandleNetifStateChanged, this); + mNetif.RegisterCallback(mNetifCallback); + + mCoapServer.AddResource(mEnergyScan); + mSocket.Open(HandleUdpReceive, this); +} + +void EnergyScanServer::HandleRequest(void *aContext, Coap::Header &aHeader, Message &aMessage, + const Ip6::MessageInfo &aMessageInfo) +{ + static_cast(aContext)->HandleRequest(aHeader, aMessage, aMessageInfo); +} + +void EnergyScanServer::HandleRequest(Coap::Header &aHeader, Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + MeshCoP::CountTlv count; + MeshCoP::PeriodTlv period; + MeshCoP::ScanDurationTlv scanDuration; + union + { + MeshCoP::ChannelMaskEntry channelMaskEntry; + uint8_t channelMaskBuf[sizeof(MeshCoP::ChannelMaskEntry) + sizeof(uint32_t)]; + }; + uint16_t offset; + uint16_t length; + + VerifyOrExit(aHeader.GetCode() == Coap::Header::kCodePost, ;); + + SuccessOrExit(MeshCoP::Tlv::GetTlv(aMessage, MeshCoP::Tlv::kCount, sizeof(count), count)); + VerifyOrExit(count.IsValid(), ;); + + SuccessOrExit(MeshCoP::Tlv::GetTlv(aMessage, MeshCoP::Tlv::kPeriod, sizeof(period), period)); + VerifyOrExit(period.IsValid(), ;); + + SuccessOrExit(MeshCoP::Tlv::GetTlv(aMessage, MeshCoP::Tlv::kScanDuration, sizeof(scanDuration), scanDuration)); + VerifyOrExit(scanDuration.IsValid(), ;); + + SuccessOrExit(MeshCoP::Tlv::GetValueOffset(aMessage, MeshCoP::Tlv::kChannelMask, offset, length)); + aMessage.Read(offset, sizeof(channelMaskBuf), channelMaskBuf); + VerifyOrExit(channelMaskEntry.GetChannelPage() == 0 && + channelMaskEntry.GetMaskLength() == sizeof(uint32_t), ;); + + mChannelMask = + (static_cast(channelMaskBuf[sizeof(MeshCoP::ChannelMaskEntry) + 0]) << 0) | + (static_cast(channelMaskBuf[sizeof(MeshCoP::ChannelMaskEntry) + 1]) << 8) | + (static_cast(channelMaskBuf[sizeof(MeshCoP::ChannelMaskEntry) + 2]) << 16) | + (static_cast(channelMaskBuf[sizeof(MeshCoP::ChannelMaskEntry) + 3]) << 24); + + mChannelMaskCurrent = mChannelMask; + mCount = count.GetCount(); + mPeriod = period.GetPeriod(); + mScanDuration = scanDuration.GetScanDuration(); + mScanResultsLength = 0; + mActive = true; + mTimer.Start(kScanDelay); + + mCommissioner = aMessageInfo.GetPeerAddr(); + SendResponse(aHeader, aMessageInfo); + +exit: + return; +} + +ThreadError EnergyScanServer::SendResponse(const Coap::Header &aRequestHeader, const Ip6::MessageInfo &aRequestInfo) +{ + ThreadError error = kThreadError_None; + Message *message; + Coap::Header responseHeader; + Ip6::MessageInfo responseInfo; + + VerifyOrExit(aRequestHeader.GetType() == Coap::Header::kTypeConfirmable, ;); + + VerifyOrExit((message = mCoapServer.NewMessage(0)) != NULL, error = kThreadError_NoBufs); + + responseHeader.Init(); + responseHeader.SetVersion(1); + responseHeader.SetType(Coap::Header::kTypeAcknowledgment); + responseHeader.SetCode(Coap::Header::kCodeChanged); + responseHeader.SetMessageId(aRequestHeader.GetMessageId()); + responseHeader.SetToken(aRequestHeader.GetToken(), aRequestHeader.GetTokenLength()); + responseHeader.Finalize(); + SuccessOrExit(error = message->Append(responseHeader.GetBytes(), responseHeader.GetLength())); + + memcpy(&responseInfo, &aRequestInfo, sizeof(responseInfo)); + memset(&responseInfo.mSockAddr, 0, sizeof(responseInfo.mSockAddr)); + SuccessOrExit(error = mCoapServer.SendMessage(*message, responseInfo)); + + otLogInfoMeshCoP("sent energy scan query response\r\n"); + +exit: + + if (error != kThreadError_None && message != NULL) + { + message->Free(); + } + + return error; +} + +void EnergyScanServer::HandleTimer(void *aContext) +{ + static_cast(aContext)->HandleTimer(); +} + +void EnergyScanServer::HandleTimer(void) +{ + VerifyOrExit(mActive, ;); + + if (mCount) + { + // grab the lowest channel to scan + uint32_t channelMask = mChannelMaskCurrent & ~(mChannelMaskCurrent - 1); + otLogInfoMeshCoP("%x\r\n", channelMask); + mNetif.GetMac().EnergyScan(channelMask, mScanDuration, HandleScanResult, this); + } + else + { + SendReport(); + } + +exit: + return; +} + +void EnergyScanServer::HandleScanResult(void *aContext, otEnergyScanResult *aResult) +{ + static_cast(aContext)->HandleScanResult(aResult); +} + +void EnergyScanServer::HandleScanResult(otEnergyScanResult *aResult) +{ + VerifyOrExit(mActive, ;); + + if (aResult) + { + mScanResults[mScanResultsLength++] = aResult->mMaxRssi; + } + else + { + // clear the lowest channel to scan + mChannelMaskCurrent &= mChannelMaskCurrent - 1; + + if (mChannelMaskCurrent == 0) + { + mChannelMaskCurrent = mChannelMask; + mCount--; + } + + if (mCount) + { + mTimer.Start(mPeriod); + } + else + { + mTimer.Start(kReportDelay); + } + } + +exit: + return; +} + +ThreadError EnergyScanServer::SendReport(void) +{ + ThreadError error = kThreadError_None; + Coap::Header header; + + OT_TOOL_PACKED_BEGIN + struct + { + MeshCoP::ChannelMaskTlv tlv; + MeshCoP::ChannelMaskEntry entry; + uint32_t mask; + } OT_TOOL_PACKED_END channelMask; + + MeshCoP::EnergyListTlv energyList; + Ip6::MessageInfo messageInfo; + Message *message; + + header.Init(); + header.SetVersion(1); + header.SetType(Coap::Header::kTypeConfirmable); + header.SetCode(Coap::Header::kCodePost); + header.SetMessageId(0); + header.SetToken(NULL, 0); + header.AppendUriPathOptions(OPENTHREAD_URI_ENERGY_REPORT); + header.AppendContentFormatOption(Coap::Header::kApplicationOctetStream); + header.Finalize(); + + VerifyOrExit((message = mSocket.NewMessage(0)) != NULL, error = kThreadError_NoBufs); + SuccessOrExit(error = message->Append(header.GetBytes(), header.GetLength())); + + channelMask.tlv.Init(); + channelMask.tlv.SetLength(sizeof(channelMask) - sizeof(channelMask.tlv)); + channelMask.entry.SetChannelPage(0); + channelMask.entry.SetMaskLength(sizeof(channelMask.mask)); + channelMask.mask = HostSwap32(mChannelMask); + SuccessOrExit(error = message->Append(&channelMask, sizeof(channelMask))); + + energyList.Init(); + energyList.SetLength(mScanResultsLength); + SuccessOrExit(error = message->Append(&energyList, sizeof(energyList))); + SuccessOrExit(error = message->Append(mScanResults, mScanResultsLength)); + + memset(&messageInfo, 0, sizeof(messageInfo)); + messageInfo.GetPeerAddr() = mCommissioner; + messageInfo.mPeerPort = kCoapUdpPort; + SuccessOrExit(error = mSocket.SendTo(*message, messageInfo)); + + otLogInfoMeshCoP("sent scan results\r\n"); + +exit: + + if (error != kThreadError_None && message != NULL) + { + message->Free(); + } + + mActive = false; + + return error; +} + +void EnergyScanServer::HandleUdpReceive(void *aContext, otMessage aMessage, const otMessageInfo *aMessageInfo) +{ + (void)aContext; + (void)aMessage; + (void)aMessageInfo; +} + +void EnergyScanServer::HandleNetifStateChanged(uint32_t aFlags, void *aContext) +{ + static_cast(aContext)->HandleNetifStateChanged(aFlags); +} + +void EnergyScanServer::HandleNetifStateChanged(uint32_t aFlags) +{ + uint8_t length; + + if ((aFlags & OT_THREAD_NETDATA_UPDATED) != 0 && + !mActive && + mNetif.GetNetworkDataLeader().GetCommissioningData(length) == NULL) + { + mActive = false; + mTimer.Stop(); + } +} + +} // namespace Thread diff --git a/src/core/thread/energy_scan_server.hpp b/src/core/thread/energy_scan_server.hpp new file mode 100644 index 000000000..5ce8db0a7 --- /dev/null +++ b/src/core/thread/energy_scan_server.hpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2016, 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. + */ + +/** + * @file + * This file includes definitions for responding to Energy Scan Requests. + */ + +#ifndef ENERGY_SCAN_SERVER_HPP_ +#define ENERGY_SCAN_SERVER_HPP_ + +#include +#include +#include +#include +#include +#include + +namespace Thread { + +class MeshForwarder; +class ThreadLastTransactionTimeTlv; +class ThreadMeshLocalEidTlv; +class ThreadNetif; +class ThreadTargetTlv; + +/** + * This class implements handling Energy Scan Requests. + * + */ +class EnergyScanServer +{ +public: + /** + * This constructor initializes the object. + * + */ + EnergyScanServer(ThreadNetif &aThreadNetif); + +private: + enum + { + kScanDelay = 1000, ///< SCAN_DELAY (milliseconds) + kReportDelay = 500, ///< Delay before sending a report (milliseconds) + }; + + static void HandleRequest(void *aContext, Coap::Header &aHeader, Message &aMessage, + const Ip6::MessageInfo &aMessageInfo); + void HandleRequest(Coap::Header &aHeader, Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + + static void HandleScanResult(void *aContext, otEnergyScanResult *aResult); + void HandleScanResult(otEnergyScanResult *aResult); + + static void HandleTimer(void *aContext); + void HandleTimer(void); + + static void HandleUdpReceive(void *aContext, otMessage aMessage, const otMessageInfo *aMessageInfo); + + static void HandleNetifStateChanged(uint32_t aFlags, void *aContext); + void HandleNetifStateChanged(uint32_t aFlags); + + ThreadError SendReport(); + ThreadError SendResponse(const Coap::Header &aRequestHeader, const Ip6::MessageInfo &aRequestMessageInfo); + + Ip6::Address mCommissioner; + uint32_t mChannelMask; + uint32_t mChannelMaskCurrent; + uint16_t mPeriod; + uint16_t mScanDuration; + uint8_t mCount; + bool mActive; + + int8_t mScanResults[OPENTHREAD_CONFIG_MAX_ENERGY_RESULTS]; + uint8_t mScanResultsLength; + + Coap::Resource mEnergyScan; + Ip6::UdpSocket mSocket; + Timer mTimer; + + Ip6::NetifCallback mNetifCallback; + + Coap::Server &mCoapServer; + ThreadNetif &mNetif; +}; + +/** + * @} + */ + +} // namespace Thread + +#endif // ENERGY_SCAN_SERVER_HPP_ diff --git a/src/core/thread/meshcop_tlvs.hpp b/src/core/thread/meshcop_tlvs.hpp index 445792516..bb72d52c0 100644 --- a/src/core/thread/meshcop_tlvs.hpp +++ b/src/core/thread/meshcop_tlvs.hpp @@ -83,6 +83,10 @@ public: kPendingTimestamp = OT_MESHCOP_TLV_PENDINGTIMESTAMP, ///< Pending Timestamp TLV kDelayTimer = OT_MESHCOP_TLV_DELAYTIMER, ///< Delay Timer TLV kChannelMask = OT_MESHCOP_TLV_CHANNELMASK, ///< Channel Mask TLV + kCount = OT_MESHCOP_TLV_COUNT, ///< Count TLV + kPeriod = OT_MESHCOP_TLV_PERIOD, ///< Period TLV + kScanDuration = OT_MESHCOP_TLV_SCAN_DURATION, ///< Scan Duration TLV + kEnergyList = OT_MESHCOP_TLV_ENERGY_LIST, ///< Energy List TLV kDiscoveryRequest = OT_MESHCOP_TLV_DISCOVERYREQUEST, ///< Discovery Request TLV kDiscoveryResponse = OT_MESHCOP_TLV_DISCOVERYRESPONSE, ///< Discovery Response TLV }; @@ -1289,6 +1293,159 @@ public: bool IsValid(void) const { return true; } } OT_TOOL_PACKED_END; +/** + * This class implements Count TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class CountTlv: public Tlv +{ +public: + /** + * This method initializes the TLV. + * + */ + void Init(void) { SetType(kCount); SetLength(sizeof(*this) - sizeof(Tlv)); } + + /** + * This method indicates whether or not the TLV appears to be well-formed. + * + * @retval TRUE If the TLV appears to be well-formed. + * @retval FALSE If the TLV does not appear to be well-formed. + * + */ + bool IsValid(void) const { return GetLength() == sizeof(*this) - sizeof(Tlv); } + + /** + * This method returns the Count value. + * + * @returns The Count value. + * + */ + uint8_t GetCount(void) const { return mCount; } + + /** + * This method sets the Count value. + * + * @param[in] aCount The Count value. + * + */ + void SetCount(uint8_t aCount) { mCount = aCount; } + +private: + uint8_t mCount; +} OT_TOOL_PACKED_END; + +/** + * This class implements Period TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class PeriodTlv: public Tlv +{ +public: + /** + * This method initializes the TLV. + * + */ + void Init(void) { SetType(kPeriod); SetLength(sizeof(*this) - sizeof(Tlv)); } + + /** + * This method indicates whether or not the TLV appears to be well-formed. + * + * @retval TRUE If the TLV appears to be well-formed. + * @retval FALSE If the TLV does not appear to be well-formed. + * + */ + bool IsValid(void) const { return GetLength() == sizeof(*this) - sizeof(Tlv); } + + /** + * This method returns the Period value. + * + * @returns The Period value. + * + */ + uint16_t GetPeriod(void) const { return HostSwap16(mPeriod); } + + /** + * This method sets the Period value. + * + * @param[in] aPeriod The Period value. + * + */ + void SetPeriod(uint16_t aPeriod) { mPeriod = HostSwap16(aPeriod); } + +private: + uint16_t mPeriod; +} OT_TOOL_PACKED_END; + +/** + * This class implements Scan Duration TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class ScanDurationTlv: public Tlv +{ +public: + /** + * This method initializes the TLV. + * + */ + void Init(void) { SetType(kScanDuration); SetLength(sizeof(*this) - sizeof(Tlv)); } + + /** + * This method indicates whether or not the TLV appears to be well-formed. + * + * @retval TRUE If the TLV appears to be well-formed. + * @retval FALSE If the TLV does not appear to be well-formed. + * + */ + bool IsValid(void) const { return GetLength() == sizeof(*this) - sizeof(Tlv); } + + /** + * This method returns the Scan Duration value. + * + * @returns The Scan Duration value. + * + */ + uint16_t GetScanDuration(void) const { return HostSwap16(mScanDuration); } + + /** + * This method sets the Scan Duration value. + * + * @param[in] aScanDuration The Scan Duration value. + * + */ + void SetScanDuration(uint16_t aScanDuration) { mScanDuration = HostSwap16(aScanDuration); } + +private: + uint16_t mScanDuration; +} OT_TOOL_PACKED_END; + +/** + * This class implements Energy List TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class EnergyListTlv: public Tlv +{ +public: + /** + * This method initializes the TLV. + * + */ + void Init(void) { SetType(kEnergyList); SetLength(sizeof(*this) - sizeof(Tlv)); } + + /** + * This method indicates whether or not the TLV appears to be well-formed. + * + * @retval TRUE If the TLV appears to be well-formed. + * @retval FALSE If the TLV does not appear to be well-formed. + * + */ + bool IsValid(void) const { return true; } +} OT_TOOL_PACKED_END; + /** * This class implements Discovery Request TLV generation and parsing. * diff --git a/src/core/thread/thread_netif.cpp b/src/core/thread/thread_netif.cpp index 8a6d597db..d597eac72 100644 --- a/src/core/thread/thread_netif.cpp +++ b/src/core/thread/thread_netif.cpp @@ -78,7 +78,8 @@ ThreadNetif::ThreadNetif(Ip6::Ip6 &aIp6): #endif // OPENTHREAD_ENABLE_JOINER mJoinerRouter(*this), mLeader(*this), - mPanIdQuery(*this) + mPanIdQuery(*this), + mEnergyScan(*this) { mKeyManager.SetMasterKey(kThreadMasterKey, sizeof(kThreadMasterKey)); } diff --git a/src/core/thread/thread_netif.hpp b/src/core/thread/thread_netif.hpp index c0cae5047..86ea2c5fa 100644 --- a/src/core/thread/thread_netif.hpp +++ b/src/core/thread/thread_netif.hpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -278,6 +279,7 @@ private: MeshCoP::JoinerRouter mJoinerRouter; MeshCoP::Leader mLeader; PanIdQueryServer mPanIdQuery; + EnergyScanServer mEnergyScan; }; /** diff --git a/src/core/thread/thread_uris.hpp b/src/core/thread/thread_uris.hpp index 40a186feb..436fce377 100644 --- a/src/core/thread/thread_uris.hpp +++ b/src/core/thread/thread_uris.hpp @@ -90,13 +90,29 @@ namespace Thread { */ #define OPENTHREAD_URI_ACTIVE_SET "c/as" +/** + * @def OPENTHREAD_URI_ENERGY_SCAN + * + * The URI Path for Energy Scan + * + */ +#define OPENTHREAD_URI_ENERGY_SCAN "c/es" + +/** + * @def OPENTHREAD_URI_ENERGY_REPORT + * + * The URI Path for Energy Report + * + */ +#define OPENTHREAD_URI_ENERGY_REPORT "c/er" + /** * @def OPENTHREAD_URI_PENDING_GET * * The URI Path for MGMT_PENDING_GET * */ -#define OPENTHREAD_URI_PENDING_GET "c/pg" +#define OPENTHREAD_URI_PENDING_GET "c/pg" /** * @def OPENTHREAD_URI_PENDING_SET @@ -104,7 +120,7 @@ namespace Thread { * The URI Path for MGMT_PENDING_SET * */ -#define OPENTHREAD_URI_PENDING_SET "c/ps" +#define OPENTHREAD_URI_PENDING_SET "c/ps" /** * @def OPENTHREAD_URI_SERVER_DATA @@ -160,7 +176,7 @@ namespace Thread { * The URI Path for Leader Keep Alive * */ -#define OPENTHREAD_URI_LEADER_KEEP_ALIVE "c/la" +#define OPENTHREAD_URI_LEADER_KEEP_ALIVE "c/la" /** * @def OPENTHREAD_URI_PANID_CONFLICT diff --git a/tests/scripts/Makefile.am b/tests/scripts/Makefile.am index 852ee6d07..e110fca2e 100644 --- a/tests/scripts/Makefile.am +++ b/tests/scripts/Makefile.am @@ -102,6 +102,7 @@ EXTRA_DIST = \ thread-cert/Cert_7_1_03_BorderRouterAsLeader.py \ thread-cert/Cert_7_1_04_BorderRouterAsRouter.py \ thread-cert/Cert_7_1_05_BorderRouterAsRouter.py \ + thread-cert/Cert_9_2_13_EnergyScan.py \ thread-cert/Cert_9_2_14_PanIdQuery.py \ thread-cert/node.py \ $(NULL) @@ -217,6 +218,7 @@ check_SCRIPTS += \ thread-cert/Cert_7_1_03_BorderRouterAsLeader.py \ thread-cert/Cert_7_1_04_BorderRouterAsRouter.py \ thread-cert/Cert_7_1_05_BorderRouterAsRouter.py \ + thread-cert/Cert_9_2_13_EnergyScan.py \ thread-cert/Cert_9_2_14_PanIdQuery.py \ $(NULL) @@ -249,6 +251,7 @@ XFAIL_NCP_TESTS = \ thread-cert/Cert_7_1_03_BorderRouterAsLeader.py \ thread-cert/Cert_7_1_04_BorderRouterAsRouter.py \ thread-cert/Cert_7_1_05_BorderRouterAsRouter.py \ + thread-cert/Cert_9_2_13_EnergyScan.py \ thread-cert/Cert_9_2_14_PanIdQuery.py \ $(NULL) diff --git a/tests/scripts/thread-cert/Cert_9_2_13_EnergyScan.py b/tests/scripts/thread-cert/Cert_9_2_13_EnergyScan.py new file mode 100755 index 000000000..fd405ea8e --- /dev/null +++ b/tests/scripts/thread-cert/Cert_9_2_13_EnergyScan.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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. +# + +import time +import unittest + +import node + +COMMISSIONER = 1 +LEADER = 2 +ROUTER1 = 3 +ED1 = 4 + +class Cert_9_2_13_EnergyScan(unittest.TestCase): + def setUp(self): + self.nodes = {} + for i in range(1,5): + self.nodes[i] = node.Node(i) + + self.nodes[COMMISSIONER].set_panid(0xface) + self.nodes[COMMISSIONER].set_mode('rsdn') + self.nodes[COMMISSIONER].add_whitelist(self.nodes[LEADER].get_addr64()) + self.nodes[COMMISSIONER].enable_whitelist() + + self.nodes[LEADER].set_panid(0xface) + self.nodes[LEADER].set_mode('rsdn') + self.nodes[LEADER].add_whitelist(self.nodes[COMMISSIONER].get_addr64()) + self.nodes[LEADER].add_whitelist(self.nodes[ROUTER1].get_addr64()) + self.nodes[LEADER].enable_whitelist() + + self.nodes[ROUTER1].set_panid(0xface) + self.nodes[ROUTER1].set_mode('rsdn') + self.nodes[ROUTER1].add_whitelist(self.nodes[LEADER].get_addr64()) + self.nodes[ROUTER1].add_whitelist(self.nodes[ED1].get_addr64()) + self.nodes[ROUTER1].enable_whitelist() + + self.nodes[ED1].set_panid(0xface) + self.nodes[ED1].set_mode('rs') + self.nodes[ED1].add_whitelist(self.nodes[ROUTER1].get_addr64()) + self.nodes[ED1].enable_whitelist() + + def tearDown(self): + for node in list(self.nodes.values()): + node.stop() + del self.nodes + + def test(self): + self.nodes[LEADER].start() + self.nodes[LEADER].set_state('leader') + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[COMMISSIONER].start() + time.sleep(3) + self.assertEqual(self.nodes[COMMISSIONER].get_state(), 'router') + + self.nodes[ROUTER1].start() + time.sleep(3) + self.assertEqual(self.nodes[ROUTER1].get_state(), 'router') + + self.nodes[ED1].start() + time.sleep(3) + self.assertEqual(self.nodes[ED1].get_state(), 'child') + + ipaddrs = self.nodes[ROUTER1].get_addrs() + for ipaddr in ipaddrs: + if ipaddr[0:4] != 'fe80': + break + + self.nodes[COMMISSIONER].ping(ipaddr) + self.nodes[COMMISSIONER].energy_scan(0x50000, 0x02, 0x20, 0x3e8, ipaddr) + + ipaddrs = self.nodes[ED1].get_addrs() + for ipaddr in ipaddrs: + if ipaddr[0:4] != 'fe80': + break + + self.nodes[COMMISSIONER].ping(ipaddr) + self.nodes[COMMISSIONER].energy_scan(0x50000, 0x02, 0x20, 0x3e8, ipaddr) + + self.nodes[COMMISSIONER].energy_scan(0x50000, 0x02, 0x20, 0x3e8, 'ff33:0040:fdde:ad00:beef:0:0:1') + + self.nodes[COMMISSIONER].ping(ipaddr) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/Cert_9_2_14_PanIdQuery.py b/tests/scripts/thread-cert/Cert_9_2_14_PanIdQuery.py index 9dea0da32..60ee641e3 100755 --- a/tests/scripts/thread-cert/Cert_9_2_14_PanIdQuery.py +++ b/tests/scripts/thread-cert/Cert_9_2_14_PanIdQuery.py @@ -92,9 +92,9 @@ class Cert_9_2_14_PanIdQuery(unittest.TestCase): if ipaddr[0:4] != 'fe80': break - self.nodes[COMMISSIONER].panid_query('0xdead', '0xffffffff', ipaddr) + self.nodes[COMMISSIONER].panid_query(0xdead, 0xffffffff, ipaddr) - self.nodes[COMMISSIONER].panid_query('0xdead', '0xffffffff', 'ff33:0040:fdde:ad00:beef:0:0:1') + self.nodes[COMMISSIONER].panid_query(0xdead, 0xffffffff, 'ff33:0040:fdde:ad00:beef:0:0:1') self.nodes[COMMISSIONER].ping(ipaddr) diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 4a5b16b60..4f6dd37f0 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -322,8 +322,13 @@ class Node: self.send_command('netdataregister') self.pexpect.expect('Done') + def energy_scan(self, mask, count, period, scan_duration, ipaddr): + cmd = 'commissioner energy ' + str(mask) + ' ' + str(count) + ' ' + str(period) + ' ' + str(scan_duration) + ' ' + ipaddr + self.send_command(cmd) + self.pexpect.expect('Energy:', timeout=8) + def panid_query(self, panid, mask, ipaddr): - cmd = 'commissioner panid ' + panid + ' ' + mask + ' ' + ipaddr + cmd = 'commissioner panid ' + str(panid) + ' ' + str(mask) + ' ' + ipaddr self.send_command(cmd) self.pexpect.expect('Conflict:', timeout=8)