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