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 "common/code_utils.hpp"
39 #include "common/locator_getters.hpp"
40 #include "common/logging.hpp"
41 #include "common/random.hpp"
42 
43 namespace ot {
44 namespace Utils {
45 
46 const uint32_t ChannelMonitor::mScanChannelMasks[kNumChannelMasks] = {
47 #if OPENTHREAD_CONFIG_RADIO_915MHZ_OQPSK_SUPPORT
48     OT_CHANNEL_1_MASK | OT_CHANNEL_5_MASK | OT_CHANNEL_9_MASK,
49     OT_CHANNEL_2_MASK | OT_CHANNEL_6_MASK | OT_CHANNEL_10_MASK,
50     OT_CHANNEL_3_MASK | OT_CHANNEL_7_MASK,
51     OT_CHANNEL_4_MASK | OT_CHANNEL_8_MASK,
52 #endif
53 #if OPENTHREAD_CONFIG_RADIO_2P4GHZ_OQPSK_SUPPORT
54     OT_CHANNEL_11_MASK | OT_CHANNEL_15_MASK | OT_CHANNEL_19_MASK | OT_CHANNEL_23_MASK,
55     OT_CHANNEL_12_MASK | OT_CHANNEL_16_MASK | OT_CHANNEL_20_MASK | OT_CHANNEL_24_MASK,
56     OT_CHANNEL_13_MASK | OT_CHANNEL_17_MASK | OT_CHANNEL_21_MASK | OT_CHANNEL_25_MASK,
57     OT_CHANNEL_14_MASK | OT_CHANNEL_18_MASK | OT_CHANNEL_22_MASK | OT_CHANNEL_26_MASK,
58 #endif
59 };
60 
ChannelMonitor(Instance & aInstance)61 ChannelMonitor::ChannelMonitor(Instance &aInstance)
62     : InstanceLocator(aInstance)
63     , mChannelMaskIndex(0)
64     , mSampleCount(0)
65     , mTimer(aInstance, ChannelMonitor::HandleTimer)
66 {
67     memset(mChannelOccupancy, 0, sizeof(mChannelOccupancy));
68 }
69 
Start(void)70 Error ChannelMonitor::Start(void)
71 {
72     Error error = kErrorNone;
73 
74     VerifyOrExit(!IsRunning(), error = kErrorAlready);
75     Clear();
76     mTimer.Start(kTimerInterval);
77     otLogDebgUtil("ChannelMonitor: Starting");
78 
79 exit:
80     return error;
81 }
82 
Stop(void)83 Error ChannelMonitor::Stop(void)
84 {
85     Error error = kErrorNone;
86 
87     VerifyOrExit(IsRunning(), error = kErrorAlready);
88     mTimer.Stop();
89     otLogDebgUtil("ChannelMonitor: Stopping");
90 
91 exit:
92     return error;
93 }
94 
Clear(void)95 void ChannelMonitor::Clear(void)
96 {
97     mChannelMaskIndex = 0;
98     mSampleCount      = 0;
99     memset(mChannelOccupancy, 0, sizeof(mChannelOccupancy));
100 
101     otLogDebgUtil("ChannelMonitor: Clearing data");
102 }
103 
GetChannelOccupancy(uint8_t aChannel) const104 uint16_t ChannelMonitor::GetChannelOccupancy(uint8_t aChannel) const
105 {
106     uint16_t occupancy = 0;
107 
108     VerifyOrExit((Radio::kChannelMin <= aChannel) && (aChannel <= Radio::kChannelMax));
109     occupancy = mChannelOccupancy[aChannel - Radio::kChannelMin];
110 
111 exit:
112     return occupancy;
113 }
114 
HandleTimer(Timer & aTimer)115 void ChannelMonitor::HandleTimer(Timer &aTimer)
116 {
117     aTimer.Get<ChannelMonitor>().HandleTimer();
118 }
119 
HandleTimer(void)120 void ChannelMonitor::HandleTimer(void)
121 {
122     IgnoreError(Get<Mac::Mac>().EnergyScan(mScanChannelMasks[mChannelMaskIndex], 0,
123                                            &ChannelMonitor::HandleEnergyScanResult, this));
124 
125     mTimer.StartAt(mTimer.GetFireTime(), Random::NonCrypto::AddJitter(kTimerInterval, kMaxJitterInterval));
126 }
127 
HandleEnergyScanResult(Mac::EnergyScanResult * aResult,void * aContext)128 void ChannelMonitor::HandleEnergyScanResult(Mac::EnergyScanResult *aResult, void *aContext)
129 {
130     static_cast<ChannelMonitor *>(aContext)->HandleEnergyScanResult(aResult);
131 }
132 
HandleEnergyScanResult(Mac::EnergyScanResult * aResult)133 void ChannelMonitor::HandleEnergyScanResult(Mac::EnergyScanResult *aResult)
134 {
135     if (aResult == nullptr)
136     {
137         if (mChannelMaskIndex == kNumChannelMasks - 1)
138         {
139             mChannelMaskIndex = 0;
140             mSampleCount++;
141             LogResults();
142         }
143         else
144         {
145             mChannelMaskIndex++;
146         }
147     }
148     else
149     {
150         uint8_t  channelIndex = (aResult->mChannel - Radio::kChannelMin);
151         uint32_t newAverage   = mChannelOccupancy[channelIndex];
152         uint32_t newValue     = 0;
153         uint32_t weight;
154 
155         OT_ASSERT(channelIndex < kNumChannels);
156 
157         otLogDebgUtil("ChannelMonitor: channel: %d, rssi:%d", aResult->mChannel, aResult->mMaxRssi);
158 
159         if (aResult->mMaxRssi != OT_RADIO_RSSI_INVALID)
160         {
161             newValue = (aResult->mMaxRssi >= kRssiThreshold) ? kMaxOccupancy : 0;
162         }
163 
164         // `mChannelOccupancy` stores the average rate/percentage of RSS
165         // samples that are higher than a given RSS threshold ("bad" RSS
166         // samples). For the first `kSampleWindow` samples, the average is
167         // maintained as the actual percentage (i.e., ratio of number of
168         // "bad" samples by total number of samples). After `kSampleWindow`
169         // samples, the averager uses an exponentially weighted moving
170         // average logic with weight coefficient `1/kSampleWindow` for new
171         // values. Practically, this means the average is representative
172         // of up to `3 * kSampleWindow` samples with highest weight given
173         // to the latest `kSampleWindow` samples.
174 
175         if (mSampleCount >= kSampleWindow)
176         {
177             weight = kSampleWindow - 1;
178         }
179         else
180         {
181             weight = mSampleCount;
182         }
183 
184         newAverage = (newAverage * weight + newValue) / (weight + 1);
185 
186         mChannelOccupancy[channelIndex] = static_cast<uint16_t>(newAverage);
187     }
188 }
189 
LogResults(void)190 void ChannelMonitor::LogResults(void)
191 {
192 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_UTIL == 1)
193     const size_t        kStringSize = 128;
194     String<kStringSize> logString;
195 
196     for (uint16_t channel : mChannelOccupancy)
197     {
198         logString.Append("%02x ", channel >> 8);
199     }
200 
201     otLogInfoUtil("ChannelMonitor: %u [%s]", mSampleCount, logString.AsCString());
202 #endif
203 }
204 
FindBestChannels(const Mac::ChannelMask & aMask,uint16_t & aOccupancy) const205 Mac::ChannelMask ChannelMonitor::FindBestChannels(const Mac::ChannelMask &aMask, uint16_t &aOccupancy) const
206 {
207     uint8_t          channel;
208     Mac::ChannelMask bestMask;
209     uint16_t         minOccupancy = 0xffff;
210 
211     bestMask.Clear();
212 
213     channel = Mac::ChannelMask::kChannelIteratorFirst;
214 
215     while (aMask.GetNextChannel(channel) == kErrorNone)
216     {
217         uint16_t occupancy = GetChannelOccupancy(channel);
218 
219         if (bestMask.IsEmpty() || (occupancy <= minOccupancy))
220         {
221             if (occupancy < minOccupancy)
222             {
223                 bestMask.Clear();
224             }
225 
226             bestMask.AddChannel(channel);
227             minOccupancy = occupancy;
228         }
229     }
230 
231     aOccupancy = minOccupancy;
232 
233     return bestMask;
234 }
235 
236 } // namespace Utils
237 } // namespace ot
238 
239 #endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
240