• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2018, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file implements the channel monitoring module.
32  */
33 
34 #include "channel_monitor.hpp"
35 
36 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
37 
38 #include "instance/instance.hpp"
39 
40 namespace ot {
41 namespace Utils {
42 
43 RegisterLogModule("ChannelMonitor");
44 
45 const uint32_t ChannelMonitor::mScanChannelMasks[kNumChannelMasks] = {
46 #if OPENTHREAD_CONFIG_RADIO_915MHZ_OQPSK_SUPPORT
47     OT_CHANNEL_1_MASK | OT_CHANNEL_5_MASK | OT_CHANNEL_9_MASK,
48     OT_CHANNEL_2_MASK | OT_CHANNEL_6_MASK | OT_CHANNEL_10_MASK,
49     OT_CHANNEL_3_MASK | OT_CHANNEL_7_MASK,
50     OT_CHANNEL_4_MASK | OT_CHANNEL_8_MASK,
51 #endif
52 #if OPENTHREAD_CONFIG_RADIO_2P4GHZ_OQPSK_SUPPORT
53     OT_CHANNEL_11_MASK | OT_CHANNEL_15_MASK | OT_CHANNEL_19_MASK | OT_CHANNEL_23_MASK,
54     OT_CHANNEL_12_MASK | OT_CHANNEL_16_MASK | OT_CHANNEL_20_MASK | OT_CHANNEL_24_MASK,
55     OT_CHANNEL_13_MASK | OT_CHANNEL_17_MASK | OT_CHANNEL_21_MASK | OT_CHANNEL_25_MASK,
56     OT_CHANNEL_14_MASK | OT_CHANNEL_18_MASK | OT_CHANNEL_22_MASK | OT_CHANNEL_26_MASK,
57 #endif
58 };
59 
ChannelMonitor(Instance & aInstance)60 ChannelMonitor::ChannelMonitor(Instance &aInstance)
61     : InstanceLocator(aInstance)
62     , mChannelMaskIndex(0)
63     , mSampleCount(0)
64     , mTimer(aInstance)
65 {
66     ClearAllBytes(mChannelOccupancy);
67 }
68 
Start(void)69 Error ChannelMonitor::Start(void)
70 {
71     Error error = kErrorNone;
72 
73     VerifyOrExit(!IsRunning(), error = kErrorAlready);
74     Clear();
75     mTimer.Start(kTimerInterval);
76     LogDebg("Starting");
77 
78 exit:
79     return error;
80 }
81 
Stop(void)82 Error ChannelMonitor::Stop(void)
83 {
84     Error error = kErrorNone;
85 
86     VerifyOrExit(IsRunning(), error = kErrorAlready);
87     mTimer.Stop();
88     LogDebg("Stopping");
89 
90 exit:
91     return error;
92 }
93 
Clear(void)94 void ChannelMonitor::Clear(void)
95 {
96     mChannelMaskIndex = 0;
97     mSampleCount      = 0;
98     ClearAllBytes(mChannelOccupancy);
99 
100     LogDebg("Clearing data");
101 }
102 
GetChannelOccupancy(uint8_t aChannel) const103 uint16_t ChannelMonitor::GetChannelOccupancy(uint8_t aChannel) const
104 {
105     uint16_t occupancy = 0;
106 
107     VerifyOrExit((Radio::kChannelMin <= aChannel) && (aChannel <= Radio::kChannelMax));
108     occupancy = mChannelOccupancy[aChannel - Radio::kChannelMin];
109 
110 exit:
111     return occupancy;
112 }
113 
HandleTimer(void)114 void ChannelMonitor::HandleTimer(void)
115 {
116     IgnoreError(Get<Mac::Mac>().EnergyScan(mScanChannelMasks[mChannelMaskIndex], 0,
117                                            &ChannelMonitor::HandleEnergyScanResult, this));
118 
119     mTimer.StartAt(mTimer.GetFireTime(), Random::NonCrypto::AddJitter(kTimerInterval, kMaxJitterInterval));
120 }
121 
HandleEnergyScanResult(Mac::EnergyScanResult * aResult,void * aContext)122 void ChannelMonitor::HandleEnergyScanResult(Mac::EnergyScanResult *aResult, void *aContext)
123 {
124     static_cast<ChannelMonitor *>(aContext)->HandleEnergyScanResult(aResult);
125 }
126 
HandleEnergyScanResult(Mac::EnergyScanResult * aResult)127 void ChannelMonitor::HandleEnergyScanResult(Mac::EnergyScanResult *aResult)
128 {
129     if (aResult == nullptr)
130     {
131         if (mChannelMaskIndex == kNumChannelMasks - 1)
132         {
133             mChannelMaskIndex = 0;
134             mSampleCount++;
135             LogResults();
136         }
137         else
138         {
139             mChannelMaskIndex++;
140         }
141     }
142     else
143     {
144         uint8_t  channelIndex = (aResult->mChannel - Radio::kChannelMin);
145         uint32_t newAverage   = mChannelOccupancy[channelIndex];
146         uint32_t newValue     = 0;
147         uint32_t weight;
148 
149         OT_ASSERT(channelIndex < kNumChannels);
150 
151         LogDebg("channel: %d, rssi:%d", aResult->mChannel, aResult->mMaxRssi);
152 
153         if (aResult->mMaxRssi != Radio::kInvalidRssi)
154         {
155             newValue = (aResult->mMaxRssi >= kRssiThreshold) ? kMaxOccupancy : 0;
156         }
157 
158         // `mChannelOccupancy` stores the average rate/percentage of RSS
159         // samples that are higher than a given RSS threshold ("bad" RSS
160         // samples). For the first `kSampleWindow` samples, the average is
161         // maintained as the actual percentage (i.e., ratio of number of
162         // "bad" samples by total number of samples). After `kSampleWindow`
163         // samples, the averager uses an exponentially weighted moving
164         // average logic with weight coefficient `1/kSampleWindow` for new
165         // values. Practically, this means the average is representative
166         // of up to `3 * kSampleWindow` samples with highest weight given
167         // to the latest `kSampleWindow` samples.
168 
169         if (mSampleCount >= kSampleWindow)
170         {
171             weight = kSampleWindow - 1;
172         }
173         else
174         {
175             weight = mSampleCount;
176         }
177 
178         newAverage = (newAverage * weight + newValue) / (weight + 1);
179 
180         mChannelOccupancy[channelIndex] = static_cast<uint16_t>(newAverage);
181     }
182 }
183 
LogResults(void)184 void ChannelMonitor::LogResults(void)
185 {
186 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
187     const size_t        kStringSize = 128;
188     String<kStringSize> logString;
189 
190     for (uint16_t channel : mChannelOccupancy)
191     {
192         logString.Append("%02x ", channel >> 8);
193     }
194 
195     LogInfo("%lu [%s]", ToUlong(mSampleCount), logString.AsCString());
196 #endif
197 }
198 
FindBestChannels(const Mac::ChannelMask & aMask,uint16_t & aOccupancy) const199 Mac::ChannelMask ChannelMonitor::FindBestChannels(const Mac::ChannelMask &aMask, uint16_t &aOccupancy) const
200 {
201     uint8_t          channel;
202     Mac::ChannelMask bestMask;
203     uint16_t         minOccupancy = 0xffff;
204 
205     bestMask.Clear();
206 
207     channel = Mac::ChannelMask::kChannelIteratorFirst;
208 
209     while (aMask.GetNextChannel(channel) == kErrorNone)
210     {
211         uint16_t occupancy = GetChannelOccupancy(channel);
212 
213         if (bestMask.IsEmpty() || (occupancy <= minOccupancy))
214         {
215             if (occupancy < minOccupancy)
216             {
217                 bestMask.Clear();
218             }
219 
220             bestMask.AddChannel(channel);
221             minOccupancy = occupancy;
222         }
223     }
224 
225     aOccupancy = minOccupancy;
226 
227     return bestMask;
228 }
229 
230 } // namespace Utils
231 } // namespace ot
232 
233 #endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
234