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