42#error tsch-cs requires tsch-stats. Please enable TSCH_STATS_CONF_ON.
45#if ! TSCH_STATS_SAMPLE_NOISE_RSSI
46#error tsch-cs requires periodic RSSI sampling. Please enable TSCH_STATS_CONF_SAMPLE_NOISE_RSSI.
51#define LOG_MODULE "TSCH CS"
52#define LOG_LEVEL LOG_LEVEL_MAC
57#define TSCH_CS_MAX_CHANNELS_CHANGED 1
60#define TSCH_CS_MIN_UPDATE_INTERVAL_SEC 60
63#define TSCH_CS_HYSTERESIS (TSCH_STATS_BINARY_SCALING_FACTOR / 10)
66#define TSCH_CS_BLACKLIST_DURATION_SEC (5 * 60)
69static bool recaculation_requested;
72static uint32_t tsch_cs_busy_since[TSCH_STATS_NUM_CHANNELS];
80static tsch_cs_bitmap_t tsch_cs_initial_bitmap;
82static tsch_cs_bitmap_t tsch_cs_current_bitmap;
85struct tsch_cs_quality {
93tsch_cs_bitmap_contains(tsch_cs_bitmap_t bitmap, uint8_t channel)
95 return (1 << (channel - TSCH_STATS_FIRST_CHANNEL)) & bitmap;
98static inline tsch_cs_bitmap_t
99tsch_cs_bitmap_set(tsch_cs_bitmap_t bitmap, uint8_t channel)
101 return (1 << (channel - TSCH_STATS_FIRST_CHANNEL)) | bitmap;
104static tsch_cs_bitmap_t
105tsch_cs_bitmap_calc(
void)
107 tsch_cs_bitmap_t result = 0;
109 for(i = 0; i < tsch_hopping_sequence_length.val; ++i) {
110 result = tsch_cs_bitmap_set(result, tsch_hopping_sequence[i]);
118 tsch_cs_initial_bitmap = tsch_cs_bitmap_calc();
119 tsch_cs_current_bitmap = tsch_cs_initial_bitmap;
124tsch_cs_bubble_sort(
struct tsch_cs_quality *qualities)
127 struct tsch_cs_quality tmp;
129 for(i = 0; i < TSCH_STATS_NUM_CHANNELS; ++i) {
130 for(j = 0; j + 1 < TSCH_STATS_NUM_CHANNELS; ++j) {
131 if(qualities[j].metric < qualities[j+1].metric){
133 qualities[j] = qualities[j+1];
134 qualities[j+1] = tmp;
142tsch_cs_select_replacement(uint8_t old_channel, tsch_stat_t old_ewma,
143 struct tsch_cs_quality *qualities, uint8_t is_in_sequence[])
147 tsch_cs_bitmap_t bitmap = tsch_cs_bitmap_set(0, old_channel);
150 old_ewma += TSCH_CS_HYSTERESIS;
153 for(i = 0; i < TSCH_STATS_NUM_CHANNELS - 1; ++i) {
155 uint8_t candidate = qualities[i].channel;
157 if(qualities[i].metric < TSCH_CS_FREE_THRESHOLD) {
162 LOG_DBG(
"ch %u: busy\n", candidate);
166 if(qualities[i].metric < old_ewma) {
168 LOG_DBG(
"ch %u: hysteresis check failed\n", candidate);
173 if(is_in_sequence[candidate - TSCH_STATS_FIRST_CHANNEL] != 0xff) {
174 LOG_DBG(
"ch %u: in seq\n", candidate);
179 if(tsch_cs_busy_since[candidate - TSCH_STATS_FIRST_CHANNEL] != 0
180 && tsch_cs_busy_since[candidate - TSCH_STATS_FIRST_CHANNEL] + TSCH_CS_BLACKLIST_DURATION_SEC > now) {
181 LOG_DBG(
"ch %u: recent bl\n", candidate);
186 if(bitmap == (tsch_cs_initial_bitmap & tsch_cs_current_bitmap)) {
188 if(!tsch_cs_bitmap_contains(tsch_cs_initial_bitmap, candidate)) {
206 struct tsch_cs_quality qualities[TSCH_STATS_NUM_CHANNELS];
207 uint8_t is_channel_busy[TSCH_STATS_NUM_CHANNELS];
208 uint8_t is_in_sequence[TSCH_STATS_NUM_CHANNELS];
209 static uint32_t last_time_changed;
211 if(!recaculation_requested) {
216 if(last_time_changed != 0 && last_time_changed + TSCH_CS_MIN_UPDATE_INTERVAL_SEC >
clock_seconds()) {
222 recaculation_requested =
false;
224 for(i = 0; i < TSCH_STATS_NUM_CHANNELS; ++i) {
225 qualities[i].channel = i + TSCH_STATS_FIRST_CHANNEL;
226 qualities[i].metric = tsch_stats.channel_free_ewma[i];
230 tsch_cs_bubble_sort(qualities);
233 for(i = 0; i < TSCH_STATS_NUM_CHANNELS; ++i) {
234 is_channel_busy[i] = (tsch_stats.channel_free_ewma[i] < TSCH_CS_FREE_THRESHOLD);
236 memset(is_in_sequence, 0xff,
sizeof(is_in_sequence));
237 for(i = 0; i < tsch_hopping_sequence_length.val; ++i) {
238 uint8_t channel = tsch_hopping_sequence[i];
239 is_in_sequence[channel - TSCH_STATS_FIRST_CHANNEL] = i;
243 for(i = 0; i < tsch_hopping_sequence_length.val; ++i) {
244 is_channel_busy[qualities[i].channel - TSCH_STATS_FIRST_CHANNEL] = 0;
247 for(i = 0; i < TSCH_STATS_NUM_CHANNELS; ++i) {
248 uint8_t ci = qualities[i].channel - TSCH_STATS_FIRST_CHANNEL;
250 LOG_DBG(
"ch %u q %u busy %u in seq %u\n",
251 qualities[i].channel,
254 is_in_sequence[ci] == 0xff ? 0 : 1);
258 for(i = 0; i < tsch_hopping_sequence_length.val; ++i) {
259 uint8_t channel = tsch_hopping_sequence[i];
260 if(is_channel_busy[channel - TSCH_STATS_FIRST_CHANNEL]) {
265 LOG_DBG(
"cs: not replacing\n");
269 has_replaced =
false;
270 for(i = TSCH_STATS_NUM_CHANNELS - 1; i >= tsch_hopping_sequence_length.val; --i) {
271 if(is_in_sequence[qualities[i].channel - TSCH_STATS_FIRST_CHANNEL] != 0xff) {
273 uint8_t channel = qualities[i].channel;
274 tsch_stat_t ewma_metric = qualities[i].metric;
275 uint8_t replacement = tsch_cs_select_replacement(channel, ewma_metric,
276 qualities, is_in_sequence);
277 uint8_t position = is_in_sequence[channel - TSCH_STATS_FIRST_CHANNEL];
279 if(replacement != 0xff) {
280 printf(
"\ncs: replacing channel %u %u (%u) with %u\n",
281 channel, tsch_hopping_sequence[position], position, replacement);
283 tsch_cs_busy_since[channel - TSCH_STATS_FIRST_CHANNEL] =
clock_seconds();
285 tsch_hopping_sequence[position] = replacement;
288 tsch_cs_current_bitmap = tsch_cs_bitmap_calc();
299 LOG_DBG(
"cs: no changes\n");
311 if(!tsch_is_coordinator) {
320 index = tsch_stats_channel_to_index(updated_channel);
322 old_is_busy = (old_busyness_metric < TSCH_CS_FREE_THRESHOLD);
323 new_is_busy = (tsch_stats.channel_free_ewma[index] < TSCH_CS_FREE_THRESHOLD);
325 if(old_is_busy != new_is_busy) {
327 recaculation_requested =
true;
329 }
else if(new_is_busy) {
331 if(tsch_cs_bitmap_contains(tsch_cs_current_bitmap, updated_channel)) {
333 recaculation_requested =
true;
unsigned long clock_seconds(void)
Get the current value of the platform seconds.
Header file for the logging system.
void tsch_cs_adaptations_init(void)
Initializes the TSCH hopping sequence selection module.
bool tsch_cs_process(void)
Potentially update the TSCH hopping sequence.
void tsch_cs_channel_stats_updated(uint8_t updated_channel, uint16_t old_busyness_metric)
Signal the need to potentially update the TSCH hopping sequence.
Header file for TSCH adaptive channel selection.
Header file for TSCH statistics.
Main API declarations for TSCH.