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