diff --git a/os/contiki-main.c b/os/contiki-main.c index c756cf206..db8893717 100644 --- a/os/contiki-main.c +++ b/os/contiki-main.c @@ -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 #include @@ -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(); diff --git a/os/net/mac/tsch/tsch.c b/os/net/mac/tsch/tsch.c index e67e11c7c..c040826ca 100644 --- a/os/net/mac/tsch/tsch.c +++ b/os/net/mac/tsch/tsch.c @@ -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); + } + } + } } } } diff --git a/os/services/tsch-cs/Makefile b/os/services/tsch-cs/Makefile new file mode 100644 index 000000000..23e2acb3d --- /dev/null +++ b/os/services/tsch-cs/Makefile @@ -0,0 +1 @@ +CFLAGS += -DBUILD_WITH_TSCH_CS=1 diff --git a/os/services/tsch-cs/tsch-cs.c b/os/services/tsch-cs/tsch-cs.c new file mode 100644 index 000000000..801f74b77 --- /dev/null +++ b/os/services/tsch-cs/tsch-cs.c @@ -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 + */ + +#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; + } + } +} diff --git a/os/services/tsch-cs/tsch-cs.h b/os/services/tsch-cs/tsch-cs.h new file mode 100644 index 000000000..900e77339 --- /dev/null +++ b/os/services/tsch-cs/tsch-cs.h @@ -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 + */ + +#ifndef __TSCH_CS_H__ +#define __TSCH_CS_H__ + +#include "contiki.h" +#include + +/* 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__ */