add TSCH channel selection service

This commit is contained in:
Atis Elsts 2017-11-28 14:58:42 +00:00
parent ad577cf77f
commit bc9967dbb0
5 changed files with 427 additions and 0 deletions

View File

@ -53,6 +53,7 @@
#include "services/orchestra/orchestra.h"
#include "services/shell/serial-shell.h"
#include "services/simple-energest/simple-energest.h"
#include "services/tsch-cs/tsch-cs.h"
#include <stdio.h>
#include <stdint.h>
@ -146,6 +147,11 @@ main(void)
simple_energest_init();
#endif /* BUILD_WITH_SIMPLE_ENERGEST */
#if BUILD_WITH_TSCH_CS
/* Initialize the channel selection module */
tsch_cs_adaptations_init();
#endif /* BUILD_WITH_TSCH_CS */
autostart_start(autostart_processes);
watchdog_start();

View File

@ -412,6 +412,22 @@ eb_input(struct input_packet *current_input)
}
#endif /* TSCH_AUTOSELECT_TIME_SOURCE */
}
/* TSCH hopping sequence */
if(eb_ies.ie_channel_hopping_sequence_id != 0) {
if(eb_ies.ie_hopping_sequence_len != tsch_hopping_sequence_length.val
|| memcmp((uint8_t *)tsch_hopping_sequence, eb_ies.ie_hopping_sequence_list, tsch_hopping_sequence_length.val)) {
if(eb_ies.ie_hopping_sequence_len <= sizeof(tsch_hopping_sequence)) {
memcpy((uint8_t *)tsch_hopping_sequence, eb_ies.ie_hopping_sequence_list,
eb_ies.ie_hopping_sequence_len);
TSCH_ASN_DIVISOR_INIT(tsch_hopping_sequence_length, eb_ies.ie_hopping_sequence_len);
LOG_WARN("Updating TSCH hopping sequence from EB\n");
} else {
LOG_WARN("TSCH:! parse_eb: hopping sequence too long (%u)\n", eb_ies.ie_hopping_sequence_len);
}
}
}
}
}
}

View File

@ -0,0 +1 @@
CFLAGS += -DBUILD_WITH_TSCH_CS=1

View File

@ -0,0 +1,328 @@
/*
* Copyright (c) 2016-2018, University of Bristol - http://www.bristol.ac.uk
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
/**
* \file
* Source file for TSCH adaptive channel selection
* \author
* Atis Elsts <atis.elsts@bristol.ac.uk>
*/
#include "tsch.h"
#include "tsch-stats.h"
#include "tsch-cs.h"
/* Log configuration */
#include "sys/log.h"
#define LOG_MODULE "TSCH CS"
#define LOG_LEVEL LOG_LEVEL_MAC
/*---------------------------------------------------------------------------*/
/* Allow to change only 1 channel at once */
#define TSCH_CS_MAX_CHANNELS_CHANGED 1
/* Do not up change channels more frequently than this */
#define TSCH_CS_MIN_UPDATE_INTERVAL_SEC 60
/* Do not change channels if the difference in qualities is below this */
#define TSCH_CS_HYSTERESIS (TSCH_STATS_BINARY_SCALING_FACTOR / 10)
/* After removing a channel from the sequence, do not add it back at least this time */
#define TSCH_CS_BLACKLIST_DURATION_SEC (5 * 60)
/* A potential for change detected? */
static bool recaculation_requested;
/* Time (in seconds) when channels were marked as busy; 0 if they are not busy */
static uint32_t tsch_cs_busy_since[TSCH_STATS_NUM_CHANNELS];
/*
* The following variables are kept in order to avoid completely migrating away
* from the initial hopping sequence (as then new nodes would not be able to join).
* The invariant is: tsch_cs_initial_bitmap & tsch_cs_current_bitmap != 0
*/
/* The bitmap with the initial channels */
static tsch_cs_bitmap_t tsch_cs_initial_bitmap;
/* The bitmap with the current channels */
static tsch_cs_bitmap_t tsch_cs_current_bitmap;
/* structure for sorting */
struct tsch_cs_quality {
/* channel number */
uint8_t channel;
/* the higher, the better */
tsch_stat_t metric;
};
/*---------------------------------------------------------------------------*/
static inline bool
tsch_cs_bitmap_contains(tsch_cs_bitmap_t bitmap, uint8_t channel)
{
return (1 << (channel - TSCH_STATS_FIRST_CHANNEL)) & bitmap;
}
/*---------------------------------------------------------------------------*/
static inline tsch_cs_bitmap_t
tsch_cs_bitmap_set(tsch_cs_bitmap_t bitmap, uint8_t channel)
{
return (1 << (channel - TSCH_STATS_FIRST_CHANNEL)) | bitmap;
}
/*---------------------------------------------------------------------------*/
static tsch_cs_bitmap_t
tsch_cs_bitmap_calc(void)
{
tsch_cs_bitmap_t result = 0;
int i;
for(i = 0; i < tsch_hopping_sequence_length.val; ++i) {
result = tsch_cs_bitmap_set(result, tsch_hopping_sequence[i]);
}
return result;
}
/*---------------------------------------------------------------------------*/
void
tsch_cs_adaptations_init(void)
{
tsch_cs_initial_bitmap = tsch_cs_bitmap_calc();
tsch_cs_current_bitmap = tsch_cs_initial_bitmap;
}
/*---------------------------------------------------------------------------*/
/* Sort the elements to that the channels with the best metrics are in the front */
static void
tsch_cs_bubble_sort(struct tsch_cs_quality *qualities)
{
int i, j;
struct tsch_cs_quality tmp;
for(i = 0; i < TSCH_STATS_NUM_CHANNELS; ++i) {
for(j = 0; j + 1 < TSCH_STATS_NUM_CHANNELS; ++j) {
if(qualities[j].metric < qualities[j+1].metric){
tmp = qualities[j];
qualities[j] = qualities[j+1];
qualities[j+1] = tmp;
}
}
}
}
/*---------------------------------------------------------------------------*/
/* Select a single, currently unused, good enough channel. Returns 0xff on failure. */
static uint8_t
tsch_cs_select_replacement(uint8_t old_channel, tsch_stat_t old_ewma,
struct tsch_cs_quality *qualities, uint8_t is_in_sequence[])
{
int i;
uint32_t now = clock_seconds();
tsch_cs_bitmap_t bitmap = tsch_cs_bitmap_set(0, old_channel);
/* Don't want to replace a channel if the improvement is miniscule (< 10%) */
old_ewma += TSCH_CS_HYSTERESIS;
/* iterate up to -1 because we know that at least one of the channels is bad */
for(i = 0; i < TSCH_STATS_NUM_CHANNELS - 1; ++i) {
/* select a replacement candidate */
uint8_t candidate = qualities[i].channel;
if(qualities[i].metric < TSCH_CS_FREE_THRESHOLD) {
/* This channel is not good enough.
* since we know that the other channels in the sorted list are even worse,
* it makes sense to return immediately rather than to continue t
*/
LOG_DBG("ch %u: busy\n", candidate);
return 0xff;
}
if(qualities[i].metric < old_ewma) {
/* not good enough to replace */
LOG_DBG("ch %u: hysteresis check failed\n", candidate);
return 0xff;
}
/* already in the current TSCH hopping sequence? */
if(is_in_sequence[candidate - TSCH_STATS_FIRST_CHANNEL] != 0xff) {
LOG_DBG("ch %u: in seq\n", candidate);
continue;
}
/* ignore this candidate if too recently blacklisted */
if(tsch_cs_busy_since[candidate - TSCH_STATS_FIRST_CHANNEL] != 0
&& tsch_cs_busy_since[candidate - TSCH_STATS_FIRST_CHANNEL] + TSCH_CS_BLACKLIST_DURATION_SEC > now) {
LOG_DBG("ch %u: recent bl\n", candidate);
continue;
}
/* check if removing the old channel would break our hopping sequence invariant */
if(bitmap == (tsch_cs_initial_bitmap & tsch_cs_current_bitmap)) {
/* the channel is the only one that belongs to both */
if(!tsch_cs_bitmap_contains(tsch_cs_initial_bitmap, candidate)) {
/* the candidate is not in the initial sequence; not acceptable */
continue;
}
}
return candidate;
}
return 0xff;
}
/*---------------------------------------------------------------------------*/
bool
tsch_cs_process(void)
{
int i;
bool try_replace;
bool has_replaced;
struct tsch_cs_quality qualities[TSCH_STATS_NUM_CHANNELS];
uint8_t is_channel_busy[TSCH_STATS_NUM_CHANNELS];
uint8_t is_in_sequence[TSCH_STATS_NUM_CHANNELS];
static uint32_t last_time_changed;
if(!recaculation_requested) {
/* nothing to do */
return false;
}
if(last_time_changed != 0 && last_time_changed + TSCH_CS_MIN_UPDATE_INTERVAL_SEC > clock_seconds()) {
/* too soon */
return false;
}
/* reset the flag */
recaculation_requested = false;
for(i = 0; i < TSCH_STATS_NUM_CHANNELS; ++i) {
qualities[i].channel = i + TSCH_STATS_FIRST_CHANNEL;
qualities[i].metric = tsch_stats.channel_free_ewma[i];
}
/* bubble sort the channels */
tsch_cs_bubble_sort(qualities);
/* start with the threshold values */
for(i = 0; i < TSCH_STATS_NUM_CHANNELS; ++i) {
is_channel_busy[i] = (tsch_stats.channel_free_ewma[i] < TSCH_CS_FREE_THRESHOLD);
}
memset(is_in_sequence, 0xff, sizeof(is_in_sequence));
for(i = 0; i < tsch_hopping_sequence_length.val; ++i) {
uint8_t channel = tsch_hopping_sequence[i];
is_in_sequence[channel - TSCH_STATS_FIRST_CHANNEL] = i;
}
/* mark the first N channels as "good" - there is nothing better to select */
for(i = 0; i < tsch_hopping_sequence_length.val; ++i) {
is_channel_busy[qualities[i].channel - TSCH_STATS_FIRST_CHANNEL] = 0;
}
for(i = 0; i < TSCH_STATS_NUM_CHANNELS; ++i) {
uint8_t ci = qualities[i].channel - TSCH_STATS_FIRST_CHANNEL;
(void)ci;
LOG_DBG("ch %u q %u busy %u in seq %u\n",
qualities[i].channel,
qualities[i].metric,
is_channel_busy[ci],
is_in_sequence[ci] == 0xff ? 0 : 1);
}
try_replace = false;
for(i = 0; i < tsch_hopping_sequence_length.val; ++i) {
uint8_t channel = tsch_hopping_sequence[i];
if(is_channel_busy[channel - TSCH_STATS_FIRST_CHANNEL]) {
try_replace = true;
}
}
if(!try_replace) {
LOG_DBG("cs: not replacing\n");
return false;
}
has_replaced = false;
for(i = TSCH_STATS_NUM_CHANNELS - 1; i >= tsch_hopping_sequence_length.val; --i) {
if(is_in_sequence[qualities[i].channel - TSCH_STATS_FIRST_CHANNEL] != 0xff) {
/* found the worst channel; it must be busy */
uint8_t channel = qualities[i].channel;
tsch_stat_t ewma_metric = qualities[i].metric;
uint8_t replacement = tsch_cs_select_replacement(channel, ewma_metric,
qualities, is_in_sequence);
uint8_t position = is_in_sequence[channel - TSCH_STATS_FIRST_CHANNEL];
if(replacement != 0xff) {
printf("\ncs: replacing channel %u %u (%u) with %u\n",
channel, tsch_hopping_sequence[position], position, replacement);
/* mark the old channel as busy */
tsch_cs_busy_since[channel - TSCH_STATS_FIRST_CHANNEL] = clock_seconds();
/* do the actual replacement in the global TSCH HS variable */
tsch_hopping_sequence[position] = replacement;
has_replaced = true;
/* recalculate the hopping sequence bitmap */
tsch_cs_current_bitmap = tsch_cs_bitmap_calc();
}
break; /* replace just one at once */
}
}
if(has_replaced) {
last_time_changed = clock_seconds();
return true;
}
LOG_DBG("cs: no changes\n");
return false;
}
/*---------------------------------------------------------------------------*/
void
tsch_cs_channel_stats_updated(uint8_t updated_channel, uint16_t old_busyness_metric)
{
uint8_t index;
bool old_is_busy;
bool new_is_busy;
/* Enable this only on the coordinator node */
if(!tsch_is_coordinator) {
return;
}
/* Do not try to adapt before enough information has been learned */
if(clock_seconds() < TSCH_CS_LEARNING_PERIOD_SEC) {
return;
}
index = tsch_stats_channel_to_index(updated_channel);
old_is_busy = (old_busyness_metric < TSCH_CS_FREE_THRESHOLD);
new_is_busy = (tsch_stats.channel_free_ewma[index] < TSCH_CS_FREE_THRESHOLD);
if(old_is_busy != new_is_busy) {
/* the status of the channel has changed*/
recaculation_requested = true;
} else if(new_is_busy) {
/* run the reselection algorithm iff the channel is both (1) bad and (2) in use */
if(tsch_cs_bitmap_contains(tsch_cs_current_bitmap, updated_channel)) {
/* the channel is in use and is busy */
recaculation_requested = true;
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2016-2018, University of Bristol - http://www.bristol.ac.uk
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
/**
* \file
* Header file for TSCH adaptive channel selection
* \author
* Atis Elsts <atis.elsts@bristol.ac.uk>
*/
#ifndef __TSCH_CS_H__
#define __TSCH_CS_H__
#include "contiki.h"
#include <stdbool.h>
/* If `channel_free_ewma` value is less than this, the channel is considered busy */
#ifdef TSCH_CS_CONF_FREE_THRESHOLD
#define TSCH_CS_FREE_THRESHOLD TSCH_CS_CONF_FREE_THRESHOLD
#else
/* < 85% free */
#define TSCH_CS_FREE_THRESHOLD ((tsch_stat_t)(85ul * TSCH_STATS_BINARY_SCALING_FACTOR / 100))
#endif
#define TSCH_CS_LEARNING_PERIOD_SEC 30
/**
* \brief Initializes the TSCH hopping sequence selection module.
*/
void tsch_cs_adaptations_init(void);
/**
* \brief Signal the need to potentially update the TSCH hopping sequence.
* \param updated_channel The channel with the updated RSSI measurement
* \param old_busyness_metric The EWMA value of the "channel busy" status before the last RSSI measurement
*/
void tsch_cs_channel_stats_updated(uint8_t updated_channel, uint16_t old_busyness_metric);
/**
* \brief Potentially update the TSCH hopping sequence
* \return true if the hopping sequence was updated, false otherwise
*/
bool tsch_cs_process(void);
/* A bit corresponds to a channel; `uint16_t` value is OK for up to 16 channels. */
typedef uint16_t tsch_cs_bitmap_t;
#endif /* __TSCH_CS_H__ */