Merge pull request #1403 from simonduq/pr/rpl-compliance-and-ofs
Compliance fixes with RFC 6550, 6551, 6552, 6719
This commit is contained in:
commit
04b618d342
|
@ -62,6 +62,7 @@
|
||||||
|
|
||||||
#include "contiki.h"
|
#include "contiki.h"
|
||||||
#include "dev/watchdog.h"
|
#include "dev/watchdog.h"
|
||||||
|
#include "net/link-stats.h"
|
||||||
#include "net/ip/tcpip.h"
|
#include "net/ip/tcpip.h"
|
||||||
#include "net/ip/uip.h"
|
#include "net/ip/uip.h"
|
||||||
#include "net/ipv6/uip-ds6.h"
|
#include "net/ipv6/uip-ds6.h"
|
||||||
|
@ -1516,6 +1517,9 @@ input(void)
|
||||||
uint8_t first_fragment = 0, last_fragment = 0;
|
uint8_t first_fragment = 0, last_fragment = 0;
|
||||||
#endif /*SICSLOWPAN_CONF_FRAG*/
|
#endif /*SICSLOWPAN_CONF_FRAG*/
|
||||||
|
|
||||||
|
/* Update link statistics */
|
||||||
|
link_stats_input_callback(packetbuf_addr(PACKETBUF_ADDR_SENDER));
|
||||||
|
|
||||||
/* init */
|
/* init */
|
||||||
uncomp_hdr_len = 0;
|
uncomp_hdr_len = 0;
|
||||||
packetbuf_hdr_len = 0;
|
packetbuf_hdr_len = 0;
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include "lib/list.h"
|
#include "lib/list.h"
|
||||||
|
#include "net/link-stats.h"
|
||||||
#include "net/linkaddr.h"
|
#include "net/linkaddr.h"
|
||||||
#include "net/packetbuf.h"
|
#include "net/packetbuf.h"
|
||||||
#include "net/ipv6/uip-ds6-nbr.h"
|
#include "net/ipv6/uip-ds6-nbr.h"
|
||||||
|
@ -74,6 +75,7 @@ NBR_TABLE_GLOBAL(uip_ds6_nbr_t, ds6_neighbors);
|
||||||
void
|
void
|
||||||
uip_ds6_neighbors_init(void)
|
uip_ds6_neighbors_init(void)
|
||||||
{
|
{
|
||||||
|
link_stats_init();
|
||||||
nbr_table_register(ds6_neighbors, (nbr_table_callback *)uip_ds6_nbr_rm);
|
nbr_table_register(ds6_neighbors, (nbr_table_callback *)uip_ds6_nbr_rm);
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
|
@ -204,6 +206,9 @@ uip_ds6_link_neighbor_callback(int status, int numtx)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Update neighbor link statistics */
|
||||||
|
link_stats_packet_sent(dest, status, numtx);
|
||||||
|
/* Call upper-layer callback (e.g. RPL) */
|
||||||
LINK_NEIGHBOR_CALLBACK(dest, status, numtx);
|
LINK_NEIGHBOR_CALLBACK(dest, status, numtx);
|
||||||
|
|
||||||
#if UIP_DS6_LL_NUD
|
#if UIP_DS6_LL_NUD
|
||||||
|
|
|
@ -71,7 +71,6 @@ typedef struct uip_ds6_nbr {
|
||||||
uip_ipaddr_t ipaddr;
|
uip_ipaddr_t ipaddr;
|
||||||
uint8_t isrouter;
|
uint8_t isrouter;
|
||||||
uint8_t state;
|
uint8_t state;
|
||||||
uint16_t link_metric;
|
|
||||||
#if UIP_ND6_SEND_NA || UIP_ND6_SEND_RA
|
#if UIP_ND6_SEND_NA || UIP_ND6_SEND_RA
|
||||||
struct stimer reachable;
|
struct stimer reachable;
|
||||||
struct stimer sendns;
|
struct stimer sendns;
|
||||||
|
|
211
core/net/link-stats.c
Normal file
211
core/net/link-stats.c
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, SICS Swedish ICT.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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 Institute 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.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Authors: Simon Duquennoy <simonduq@sics.se>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "contiki.h"
|
||||||
|
#include "sys/clock.h"
|
||||||
|
#include "net/packetbuf.h"
|
||||||
|
#include "net/nbr-table.h"
|
||||||
|
#include "net/link-stats.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define DEBUG 0
|
||||||
|
#if DEBUG
|
||||||
|
#define PRINTF(...) printf(__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define PRINTF(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Half time for the freshness counter, in minutes */
|
||||||
|
#define FRESHNESS_HALF_LIFE 20
|
||||||
|
/* Statistics are fresh if the freshness counter is FRESHNESS_TARGET or more */
|
||||||
|
#define FRESHNESS_TARGET 4
|
||||||
|
/* Maximum value for the freshness counter */
|
||||||
|
#define FRESHNESS_MAX 16
|
||||||
|
/* Statistics with no update in FRESHNESS_EXPIRATION_TIMEOUT is not fresh */
|
||||||
|
#define FRESHNESS_EXPIRATION_TIME (10 * 60 * CLOCK_SECOND)
|
||||||
|
|
||||||
|
/* EWMA (exponential moving average) used to maintain statistics over time */
|
||||||
|
#define EWMA_SCALE 100
|
||||||
|
#define EWMA_ALPHA 15
|
||||||
|
#define EWMA_BOOTSTRAP_ALPHA 30
|
||||||
|
|
||||||
|
/* ETX fixed point divisor. 128 is the value used by RPL (RFC 6551 and RFC 6719) */
|
||||||
|
#define ETX_DIVISOR LINK_STATS_ETX_DIVISOR
|
||||||
|
/* Number of Tx used to update the ETX EWMA in case of no-ACK */
|
||||||
|
#define ETX_NOACK_PENALTY 10
|
||||||
|
/* Initial ETX value */
|
||||||
|
#define ETX_INIT 2
|
||||||
|
|
||||||
|
/* Per-neighbor link statistics table */
|
||||||
|
NBR_TABLE(struct link_stats, link_stats);
|
||||||
|
|
||||||
|
/* Called every FRESHNESS_HALF_LIFE minutes */
|
||||||
|
struct ctimer periodic_timer;
|
||||||
|
|
||||||
|
/* Used to initialize ETX before any transmission occurs. In order to
|
||||||
|
* infer the initial ETX from the RSSI of previously received packets, use: */
|
||||||
|
/* #define LINK_STATS_CONF_INIT_ETX(stats) guess_etx_from_rssi(stats) */
|
||||||
|
|
||||||
|
#ifdef LINK_STATS_CONF_INIT_ETX
|
||||||
|
#define LINK_STATS_INIT_ETX(stats) LINK_STATS_CONF_INIT_ETX(stats)
|
||||||
|
#else /* LINK_STATS_INIT_ETX */
|
||||||
|
#define LINK_STATS_INIT_ETX(stats) (ETX_INIT * ETX_DIVISOR)
|
||||||
|
#endif /* LINK_STATS_INIT_ETX */
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Returns the neighbor's link stats */
|
||||||
|
const struct link_stats *
|
||||||
|
link_stats_from_lladdr(const linkaddr_t *lladdr)
|
||||||
|
{
|
||||||
|
return nbr_table_get_from_lladdr(link_stats, lladdr);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Are the statistics fresh? */
|
||||||
|
int
|
||||||
|
link_stats_is_fresh(const struct link_stats *stats)
|
||||||
|
{
|
||||||
|
return (stats != NULL)
|
||||||
|
&& clock_time() - stats->last_tx_time < FRESHNESS_EXPIRATION_TIME
|
||||||
|
&& stats->freshness >= FRESHNESS_TARGET;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
uint16_t
|
||||||
|
guess_etx_from_rssi(const struct link_stats *stats)
|
||||||
|
{
|
||||||
|
if(stats != NULL) {
|
||||||
|
if(stats->rssi == 0) {
|
||||||
|
return ETX_INIT * ETX_DIVISOR;
|
||||||
|
} else {
|
||||||
|
/* A rough estimate of PRR from RSSI, as a linear function where:
|
||||||
|
* RSSI >= -60 results in PRR of 1
|
||||||
|
* RSSI <= -90 results in PRR of 0
|
||||||
|
* prr = (bounded_rssi - RSSI_LOW) / (RSSI_DIFF)
|
||||||
|
* etx = ETX_DIVOSOR / ((bounded_rssi - RSSI_LOW) / RSSI_DIFF)
|
||||||
|
* etx = (RSSI_DIFF * ETX_DIVOSOR) / (bounded_rssi - RSSI_LOW)
|
||||||
|
* */
|
||||||
|
#define ETX_INIT_MAX 3
|
||||||
|
#define RSSI_HIGH -60
|
||||||
|
#define RSSI_LOW -90
|
||||||
|
#define RSSI_DIFF (RSSI_HIGH - RSSI_LOW)
|
||||||
|
uint16_t etx;
|
||||||
|
int16_t bounded_rssi = stats->rssi;
|
||||||
|
bounded_rssi = MIN(bounded_rssi, RSSI_HIGH);
|
||||||
|
bounded_rssi = MAX(bounded_rssi, RSSI_LOW + 1);
|
||||||
|
etx = RSSI_DIFF * ETX_DIVISOR / (bounded_rssi - RSSI_LOW);
|
||||||
|
return MIN(etx, ETX_INIT_MAX * ETX_DIVISOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0xffff;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Packet sent callback. Updates stats for transmissions to lladdr */
|
||||||
|
void
|
||||||
|
link_stats_packet_sent(const linkaddr_t *lladdr, int status, int numtx)
|
||||||
|
{
|
||||||
|
struct link_stats *stats;
|
||||||
|
uint16_t packet_etx;
|
||||||
|
uint8_t ewma_alpha;
|
||||||
|
|
||||||
|
if(status != MAC_TX_OK && status != MAC_TX_NOACK) {
|
||||||
|
/* Do not penalize the ETX when collisions or transmission errors occur. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stats = nbr_table_get_from_lladdr(link_stats, lladdr);
|
||||||
|
if(stats == NULL) {
|
||||||
|
/* Add the neighbor */
|
||||||
|
stats = nbr_table_add_lladdr(link_stats, lladdr, NBR_TABLE_REASON_LINK_STATS, NULL);
|
||||||
|
if(stats != NULL) {
|
||||||
|
stats->etx = LINK_STATS_INIT_ETX(stats);
|
||||||
|
} else {
|
||||||
|
return; /* No space left, return */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update last timestamp and freshness */
|
||||||
|
stats->last_tx_time = clock_time();
|
||||||
|
stats->freshness = MIN(stats->freshness + numtx, FRESHNESS_MAX);
|
||||||
|
|
||||||
|
/* ETX used for this update */
|
||||||
|
packet_etx = ((status == MAC_TX_NOACK) ? ETX_NOACK_PENALTY : numtx) * ETX_DIVISOR;
|
||||||
|
/* ETX alpha used for this update */
|
||||||
|
ewma_alpha = link_stats_is_fresh(stats) ? EWMA_ALPHA : EWMA_BOOTSTRAP_ALPHA;
|
||||||
|
|
||||||
|
/* Compute EWMA and update ETX */
|
||||||
|
stats->etx = ((uint32_t)stats->etx * (EWMA_SCALE - ewma_alpha) +
|
||||||
|
(uint32_t)packet_etx * ewma_alpha) / EWMA_SCALE;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Packet input callback. Updates statistics for receptions on a given link */
|
||||||
|
void
|
||||||
|
link_stats_input_callback(const linkaddr_t *lladdr)
|
||||||
|
{
|
||||||
|
struct link_stats *stats;
|
||||||
|
int16_t packet_rssi = packetbuf_attr(PACKETBUF_ATTR_RSSI);
|
||||||
|
|
||||||
|
stats = nbr_table_get_from_lladdr(link_stats, lladdr);
|
||||||
|
if(stats == NULL) {
|
||||||
|
/* Add the neighbor */
|
||||||
|
stats = nbr_table_add_lladdr(link_stats, lladdr, NBR_TABLE_REASON_LINK_STATS, NULL);
|
||||||
|
if(stats != NULL) {
|
||||||
|
/* Initialize */
|
||||||
|
stats->rssi = packet_rssi;
|
||||||
|
stats->etx = LINK_STATS_INIT_ETX(stats);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update RSSI EWMA */
|
||||||
|
stats->rssi = ((int32_t)stats->rssi * (EWMA_SCALE - EWMA_ALPHA) +
|
||||||
|
(int32_t)packet_rssi * EWMA_ALPHA) / EWMA_SCALE;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Periodic timer called every FRESHNESS_HALF_LIFE minutes */
|
||||||
|
static void
|
||||||
|
periodic(void *ptr)
|
||||||
|
{
|
||||||
|
/* Age (by halving) freshness counter of all neighbors */
|
||||||
|
struct link_stats *stats;
|
||||||
|
ctimer_reset(&periodic_timer);
|
||||||
|
for(stats = nbr_table_head(link_stats); stats != NULL; stats = nbr_table_next(link_stats, stats)) {
|
||||||
|
stats->freshness >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Initializes link-stats module */
|
||||||
|
void
|
||||||
|
link_stats_init(void)
|
||||||
|
{
|
||||||
|
nbr_table_register(link_stats, NULL);
|
||||||
|
ctimer_set(&periodic_timer, 60 * CLOCK_SECOND * FRESHNESS_HALF_LIFE,
|
||||||
|
periodic, NULL);
|
||||||
|
}
|
65
core/net/link-stats.h
Normal file
65
core/net/link-stats.h
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, SICS Swedish ICT.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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 Institute 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.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Authors: Simon Duquennoy <simonduq@sics.se>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LINK_STATS_H_
|
||||||
|
#define LINK_STATS_H_
|
||||||
|
|
||||||
|
#include "core/net/linkaddr.h"
|
||||||
|
|
||||||
|
/* ETX fixed point divisor. 128 is the value used by RPL (RFC 6551 and RFC 6719) */
|
||||||
|
#ifdef LINK_STATS_CONF_ETX_DIVISOR
|
||||||
|
#define LINK_STATS_ETX_DIVISOR LINK_STATS_CONF_ETX_DIVISOR
|
||||||
|
#else /* LINK_STATS_CONF_ETX_DIVISOR */
|
||||||
|
#define LINK_STATS_ETX_DIVISOR 128
|
||||||
|
#endif /* LINK_STATS_CONF_ETX_DIVISOR */
|
||||||
|
|
||||||
|
/* All statistics of a given link */
|
||||||
|
struct link_stats {
|
||||||
|
uint16_t etx; /* ETX using ETX_DIVISOR as fixed point divisor */
|
||||||
|
int16_t rssi; /* RSSI (received signal strength) */
|
||||||
|
uint8_t freshness; /* Freshness of the statistics */
|
||||||
|
clock_time_t last_tx_time; /* Last Tx timestamp */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Returns the neighbor's link statistics */
|
||||||
|
const struct link_stats *link_stats_from_lladdr(const linkaddr_t *lladdr);
|
||||||
|
/* Are the statistics fresh? */
|
||||||
|
int link_stats_is_fresh(const struct link_stats *stats);
|
||||||
|
|
||||||
|
/* Initializes link-stats module */
|
||||||
|
void link_stats_init(void);
|
||||||
|
/* Packet sent callback. Updates statistics for transmissions on a given link */
|
||||||
|
void link_stats_packet_sent(const linkaddr_t *lladdr, int status, int numtx);
|
||||||
|
/* Packet input callback. Updates statistics for receptions on a given link */
|
||||||
|
void link_stats_input_callback(const linkaddr_t *lladdr);
|
||||||
|
|
||||||
|
#endif /* LINK_STATS_H_ */
|
|
@ -83,7 +83,8 @@ typedef enum {
|
||||||
NBR_TABLE_REASON_ROUTE,
|
NBR_TABLE_REASON_ROUTE,
|
||||||
NBR_TABLE_REASON_IPV6_ND,
|
NBR_TABLE_REASON_IPV6_ND,
|
||||||
NBR_TABLE_REASON_MAC,
|
NBR_TABLE_REASON_MAC,
|
||||||
NBR_TABLE_REASON_LLSEC
|
NBR_TABLE_REASON_LLSEC,
|
||||||
|
NBR_TABLE_REASON_LINK_STATS,
|
||||||
} nbr_table_reason_t;
|
} nbr_table_reason_t;
|
||||||
|
|
||||||
/** \name Neighbor tables: register and loop through table elements */
|
/** \name Neighbor tables: register and loop through table elements */
|
||||||
|
|
|
@ -46,30 +46,51 @@
|
||||||
#endif /* RPL_CONF_STATS */
|
#endif /* RPL_CONF_STATS */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Select routing metric supported at runtime. This must be a valid
|
* The objective function (OF) used by a RPL root is configurable through
|
||||||
* DAG Metric Container Object Type (see below). Currently, we only
|
* the RPL_CONF_OF_OCP parameter. This is defined as the objective code
|
||||||
* support RPL_DAG_MC_ETX and RPL_DAG_MC_ENERGY.
|
* point (OCP) of the OF, RPL_OCP_OF0 or RPL_OCP_MRHOF. This flag is of
|
||||||
* When MRHOF (RFC6719) is used with ETX, no metric container must
|
* no relevance to non-root nodes, which run the OF advertised in the
|
||||||
* be used; instead the rank carries ETX directly.
|
* instance they join.
|
||||||
|
* Make sure the selected of is inRPL_SUPPORTED_OFS.
|
||||||
*/
|
*/
|
||||||
|
#ifdef RPL_CONF_OF_OCP
|
||||||
|
#define RPL_OF_OCP RPL_CONF_OF_OCP
|
||||||
|
#else /* RPL_CONF_OF_OCP */
|
||||||
|
#define RPL_OF_OCP RPL_OCP_MRHOF
|
||||||
|
#endif /* RPL_CONF_OF_OCP */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The set of objective functions supported at runtime. Nodes are only
|
||||||
|
* able to join instances that advertise an OF in this set. To include
|
||||||
|
* both OF0 and MRHOF, use {&rpl_of0, &rpl_mrhof}.
|
||||||
|
*/
|
||||||
|
#ifdef RPL_CONF_SUPPORTED_OFS
|
||||||
|
#define RPL_SUPPORTED_OFS RPL_CONF_SUPPORTED_OFS
|
||||||
|
#else /* RPL_CONF_SUPPORTED_OFS */
|
||||||
|
#define RPL_SUPPORTED_OFS {&rpl_mrhof}
|
||||||
|
#endif /* RPL_CONF_SUPPORTED_OFS */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enable/disable RPL Metric Containers (MC). The actual MC in use
|
||||||
|
* for a given DODAG is decided at runtime, when joining. Note that
|
||||||
|
* OF0 (RFC6552) operates without MC, and so does MRHOF (RFC6719) when
|
||||||
|
* used with ETX as a metric (the rank is the metric). We disable MC
|
||||||
|
* by default, but note it must be enabled to support joining a DODAG
|
||||||
|
* that requires MC (e.g., MRHOF with a metric other than ETX).
|
||||||
|
*/
|
||||||
|
#ifdef RPL_CONF_WITH_MC
|
||||||
|
#define RPL_WITH_MC RPL_CONF_WITH_MC
|
||||||
|
#else /* RPL_CONF_WITH_MC */
|
||||||
|
#define RPL_WITH_MC 0
|
||||||
|
#endif /* RPL_CONF_WITH_MC */
|
||||||
|
|
||||||
|
/* The MC advertised in DIOs and propagating from the root */
|
||||||
#ifdef RPL_CONF_DAG_MC
|
#ifdef RPL_CONF_DAG_MC
|
||||||
#define RPL_DAG_MC RPL_CONF_DAG_MC
|
#define RPL_DAG_MC RPL_CONF_DAG_MC
|
||||||
#else
|
#else
|
||||||
#define RPL_DAG_MC RPL_DAG_MC_NONE
|
#define RPL_DAG_MC RPL_DAG_MC_NONE
|
||||||
#endif /* RPL_CONF_DAG_MC */
|
#endif /* RPL_CONF_DAG_MC */
|
||||||
|
|
||||||
/*
|
|
||||||
* The objective function used by RPL is configurable through the
|
|
||||||
* RPL_CONF_OF parameter. This should be defined to be the name of an
|
|
||||||
* rpl_of object linked into the system image, e.g., rpl_of0.
|
|
||||||
*/
|
|
||||||
#ifdef RPL_CONF_OF
|
|
||||||
#define RPL_OF RPL_CONF_OF
|
|
||||||
#else
|
|
||||||
/* ETX is the default objective function. */
|
|
||||||
#define RPL_OF rpl_mrhof
|
|
||||||
#endif /* RPL_CONF_OF */
|
|
||||||
|
|
||||||
/* This value decides which DAG instance we should participate in by default. */
|
/* This value decides which DAG instance we should participate in by default. */
|
||||||
#ifdef RPL_CONF_DEFAULT_INSTANCE
|
#ifdef RPL_CONF_DEFAULT_INSTANCE
|
||||||
#define RPL_DEFAULT_INSTANCE RPL_CONF_DEFAULT_INSTANCE
|
#define RPL_DEFAULT_INSTANCE RPL_CONF_DEFAULT_INSTANCE
|
||||||
|
@ -186,15 +207,6 @@
|
||||||
#define RPL_DIO_REDUNDANCY 10
|
#define RPL_DIO_REDUNDANCY 10
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
|
||||||
* Initial metric attributed to a link when the ETX is unknown
|
|
||||||
*/
|
|
||||||
#ifndef RPL_CONF_INIT_LINK_METRIC
|
|
||||||
#define RPL_INIT_LINK_METRIC 2
|
|
||||||
#else
|
|
||||||
#define RPL_INIT_LINK_METRIC RPL_CONF_INIT_LINK_METRIC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Default route lifetime unit. This is the granularity of time
|
* Default route lifetime unit. This is the granularity of time
|
||||||
* used in RPL lifetime values, in seconds.
|
* used in RPL lifetime values, in seconds.
|
||||||
|
@ -287,22 +299,13 @@
|
||||||
#define RPL_PROBING_INTERVAL (120 * CLOCK_SECOND)
|
#define RPL_PROBING_INTERVAL (120 * CLOCK_SECOND)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
|
||||||
* RPL probing expiration time.
|
|
||||||
*/
|
|
||||||
#ifdef RPL_CONF_PROBING_EXPIRATION_TIME
|
|
||||||
#define RPL_PROBING_EXPIRATION_TIME RPL_CONF_PROBING_EXPIRATION_TIME
|
|
||||||
#else
|
|
||||||
#define RPL_PROBING_EXPIRATION_TIME (10 * 60 * CLOCK_SECOND)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Function used to select the next parent to be probed.
|
* Function used to select the next parent to be probed.
|
||||||
*/
|
*/
|
||||||
#ifdef RPL_CONF_PROBING_SELECT_FUNC
|
#ifdef RPL_CONF_PROBING_SELECT_FUNC
|
||||||
#define RPL_PROBING_SELECT_FUNC RPL_CONF_PROBING_SELECT_FUNC
|
#define RPL_PROBING_SELECT_FUNC RPL_CONF_PROBING_SELECT_FUNC
|
||||||
#else
|
#else
|
||||||
#define RPL_PROBING_SELECT_FUNC(dag) get_probing_target((dag))
|
#define RPL_PROBING_SELECT_FUNC get_probing_target
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -325,8 +328,7 @@
|
||||||
#ifdef RPL_CONF_PROBING_DELAY_FUNC
|
#ifdef RPL_CONF_PROBING_DELAY_FUNC
|
||||||
#define RPL_PROBING_DELAY_FUNC RPL_CONF_PROBING_DELAY_FUNC
|
#define RPL_PROBING_DELAY_FUNC RPL_CONF_PROBING_DELAY_FUNC
|
||||||
#else
|
#else
|
||||||
#define RPL_PROBING_DELAY_FUNC() ((RPL_PROBING_INTERVAL / 2) \
|
#define RPL_PROBING_DELAY_FUNC get_probing_delay
|
||||||
+ random_rand() % (RPL_PROBING_INTERVAL))
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "contiki.h"
|
#include "contiki.h"
|
||||||
|
#include "net/link-stats.h"
|
||||||
#include "net/rpl/rpl-private.h"
|
#include "net/rpl/rpl-private.h"
|
||||||
#include "net/ip/uip.h"
|
#include "net/ip/uip.h"
|
||||||
#include "net/ipv6/uip-nd6.h"
|
#include "net/ipv6/uip-nd6.h"
|
||||||
|
@ -66,8 +67,8 @@ void RPL_CALLBACK_PARENT_SWITCH(rpl_parent_t *old, rpl_parent_t *new);
|
||||||
#endif /* RPL_CALLBACK_PARENT_SWITCH */
|
#endif /* RPL_CALLBACK_PARENT_SWITCH */
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
extern rpl_of_t RPL_OF;
|
extern rpl_of_t rpl_of0, rpl_mrhof;
|
||||||
static rpl_of_t * const objective_functions[] = {&RPL_OF};
|
static rpl_of_t * const objective_functions[] = RPL_SUPPORTED_OFS;
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
/* RPL definitions. */
|
/* RPL definitions. */
|
||||||
|
@ -91,22 +92,26 @@ void
|
||||||
rpl_print_neighbor_list(void)
|
rpl_print_neighbor_list(void)
|
||||||
{
|
{
|
||||||
if(default_instance != NULL && default_instance->current_dag != NULL &&
|
if(default_instance != NULL && default_instance->current_dag != NULL &&
|
||||||
default_instance->of != NULL && default_instance->of->calculate_rank != NULL) {
|
default_instance->of != NULL) {
|
||||||
int curr_dio_interval = default_instance->dio_intcurrent;
|
int curr_dio_interval = default_instance->dio_intcurrent;
|
||||||
int curr_rank = default_instance->current_dag->rank;
|
int curr_rank = default_instance->current_dag->rank;
|
||||||
rpl_parent_t *p = nbr_table_head(rpl_parents);
|
rpl_parent_t *p = nbr_table_head(rpl_parents);
|
||||||
clock_time_t now = clock_time();
|
clock_time_t clock_now = clock_time();
|
||||||
|
|
||||||
printf("RPL: rank %u dioint %u, %u nbr(s)\n", curr_rank, curr_dio_interval, uip_ds6_nbr_num());
|
printf("RPL: OCP %u rank %u dioint %u, nbr count %u\n",
|
||||||
|
default_instance->of->ocp, curr_rank, curr_dio_interval, uip_ds6_nbr_num());
|
||||||
while(p != NULL) {
|
while(p != NULL) {
|
||||||
uip_ds6_nbr_t *nbr = rpl_get_nbr(p);
|
const struct link_stats *stats = rpl_get_parent_link_stats(p);
|
||||||
printf("RPL: nbr %3u %5u, %5u => %5u %c%c (last tx %u min ago)\n",
|
printf("RPL: nbr %3u %5u, %5u => %5u -- %2u %c%c (last tx %u min ago)\n",
|
||||||
nbr_table_get_lladdr(rpl_parents, p)->u8[7],
|
rpl_get_parent_ipaddr(p)->u8[15],
|
||||||
p->rank, nbr ? nbr->link_metric : 0,
|
p->rank,
|
||||||
default_instance->of->calculate_rank(p, 0),
|
rpl_get_parent_link_metric(p),
|
||||||
default_instance->current_dag == p->dag ? 'd' : ' ',
|
rpl_rank_via_parent(p),
|
||||||
p == default_instance->current_dag->preferred_parent ? '*' : ' ',
|
stats != NULL ? stats->freshness : 0,
|
||||||
(unsigned)((now - p->last_tx_time) / (60 * CLOCK_SECOND)));
|
link_stats_is_fresh(stats) ? 'f' : ' ',
|
||||||
|
p == default_instance->current_dag->preferred_parent ? 'p' : ' ',
|
||||||
|
(unsigned)((clock_now - stats->last_tx_time) / (60 * CLOCK_SECOND))
|
||||||
|
);
|
||||||
p = nbr_table_next(rpl_parents, p);
|
p = nbr_table_next(rpl_parents, p);
|
||||||
}
|
}
|
||||||
printf("RPL: end of list\n");
|
printf("RPL: end of list\n");
|
||||||
|
@ -116,8 +121,7 @@ rpl_print_neighbor_list(void)
|
||||||
uip_ds6_nbr_t *
|
uip_ds6_nbr_t *
|
||||||
rpl_get_nbr(rpl_parent_t *parent)
|
rpl_get_nbr(rpl_parent_t *parent)
|
||||||
{
|
{
|
||||||
linkaddr_t *lladdr = NULL;
|
const linkaddr_t *lladdr = rpl_get_parent_lladdr(parent);
|
||||||
lladdr = nbr_table_get_lladdr(rpl_parents, parent);
|
|
||||||
if(lladdr != NULL) {
|
if(lladdr != NULL) {
|
||||||
return nbr_table_get_from_lladdr(ds6_neighbors, lladdr);
|
return nbr_table_get_from_lladdr(ds6_neighbors, lladdr);
|
||||||
} else {
|
} else {
|
||||||
|
@ -151,30 +155,78 @@ rpl_get_parent_rank(uip_lladdr_t *addr)
|
||||||
if(p != NULL) {
|
if(p != NULL) {
|
||||||
return p->rank;
|
return p->rank;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return INFINITE_RANK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
uint16_t
|
uint16_t
|
||||||
rpl_get_parent_link_metric(const uip_lladdr_t *addr)
|
rpl_get_parent_link_metric(rpl_parent_t *p)
|
||||||
{
|
{
|
||||||
uip_ds6_nbr_t *nbr;
|
if(p != NULL && p->dag != NULL) {
|
||||||
nbr = nbr_table_get_from_lladdr(ds6_neighbors, (const linkaddr_t *)addr);
|
rpl_instance_t *instance = p->dag->instance;
|
||||||
|
if(instance != NULL && instance->of != NULL && instance->of->parent_link_metric != NULL) {
|
||||||
if(nbr != NULL) {
|
return instance->of->parent_link_metric(p);
|
||||||
return nbr->link_metric;
|
}
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
return 0xffff;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
rpl_rank_t
|
||||||
|
rpl_rank_via_parent(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
if(p != NULL && p->dag != NULL) {
|
||||||
|
rpl_instance_t *instance = p->dag->instance;
|
||||||
|
if(instance != NULL && instance->of != NULL && instance->of->rank_via_parent != NULL) {
|
||||||
|
return instance->of->rank_via_parent(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INFINITE_RANK;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
const linkaddr_t *
|
||||||
|
rpl_get_parent_lladdr(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
return nbr_table_get_lladdr(rpl_parents, p);
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
uip_ipaddr_t *
|
uip_ipaddr_t *
|
||||||
rpl_get_parent_ipaddr(rpl_parent_t *p)
|
rpl_get_parent_ipaddr(rpl_parent_t *p)
|
||||||
{
|
{
|
||||||
linkaddr_t *lladdr = nbr_table_get_lladdr(rpl_parents, p);
|
const linkaddr_t *lladdr = rpl_get_parent_lladdr(p);
|
||||||
return uip_ds6_nbr_ipaddr_from_lladdr((uip_lladdr_t *)lladdr);
|
return uip_ds6_nbr_ipaddr_from_lladdr((uip_lladdr_t *)lladdr);
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
|
const struct link_stats *
|
||||||
|
rpl_get_parent_link_stats(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
const linkaddr_t *lladdr = rpl_get_parent_lladdr(p);
|
||||||
|
return link_stats_from_lladdr(lladdr);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
rpl_parent_is_fresh(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
const struct link_stats *stats = rpl_get_parent_link_stats(p);
|
||||||
|
return link_stats_is_fresh(stats);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
rpl_parent_is_reachable(rpl_parent_t *p) {
|
||||||
|
if(p == NULL || p->dag == NULL || p->dag->instance == NULL || p->dag->instance->of == NULL) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
#ifndef UIP_CONF_ND6_SEND_NA
|
||||||
|
uip_ds6_nbr_t *nbr = rpl_get_nbr(p);
|
||||||
|
/* Exclude links to a neighbor that is not reachable at a NUD level */
|
||||||
|
if(nbr == NULL || nbr->state != NBR_REACHABLE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* UIP_CONF_ND6_SEND_NA */
|
||||||
|
/* If we don't have fresh link information, assume the parent is reachable. */
|
||||||
|
return !rpl_parent_is_fresh(p) || p->dag->instance->of->parent_has_usable_link(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static void
|
static void
|
||||||
rpl_set_preferred_parent(rpl_dag_t *dag, rpl_parent_t *p)
|
rpl_set_preferred_parent(rpl_dag_t *dag, rpl_parent_t *p)
|
||||||
{
|
{
|
||||||
|
@ -340,7 +392,12 @@ rpl_set_root(uint8_t instance_id, uip_ipaddr_t *dag_id)
|
||||||
dag->grounded = RPL_GROUNDED;
|
dag->grounded = RPL_GROUNDED;
|
||||||
dag->preference = RPL_PREFERENCE;
|
dag->preference = RPL_PREFERENCE;
|
||||||
instance->mop = RPL_MOP_DEFAULT;
|
instance->mop = RPL_MOP_DEFAULT;
|
||||||
instance->of = &RPL_OF;
|
instance->of = rpl_find_of(RPL_OF_OCP);
|
||||||
|
if(instance->of == NULL) {
|
||||||
|
PRINTF("RPL: OF with OCP %u not supported\n", RPL_OF_OCP);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
rpl_set_preferred_parent(dag, NULL);
|
rpl_set_preferred_parent(dag, NULL);
|
||||||
|
|
||||||
memcpy(&dag->dag_id, dag_id, sizeof(dag->dag_id));
|
memcpy(&dag->dag_id, dag_id, sizeof(dag->dag_id));
|
||||||
|
@ -640,20 +697,12 @@ rpl_add_parent(rpl_dag_t *dag, rpl_dio_t *dio, uip_ipaddr_t *addr)
|
||||||
if(p == NULL) {
|
if(p == NULL) {
|
||||||
PRINTF("RPL: rpl_add_parent p NULL\n");
|
PRINTF("RPL: rpl_add_parent p NULL\n");
|
||||||
} else {
|
} else {
|
||||||
uip_ds6_nbr_t *nbr;
|
|
||||||
nbr = rpl_get_nbr(p);
|
|
||||||
|
|
||||||
p->dag = dag;
|
p->dag = dag;
|
||||||
p->rank = dio->rank;
|
p->rank = dio->rank;
|
||||||
p->dtsn = dio->dtsn;
|
p->dtsn = dio->dtsn;
|
||||||
|
#if RPL_WITH_MC
|
||||||
/* Check whether we have a neighbor that has not gotten a link metric yet */
|
|
||||||
if(nbr != NULL && nbr->link_metric == 0) {
|
|
||||||
nbr->link_metric = RPL_INIT_LINK_METRIC * RPL_DAG_MC_ETX_DIVISOR;
|
|
||||||
}
|
|
||||||
#if RPL_DAG_MC != RPL_DAG_MC_NONE
|
|
||||||
memcpy(&p->mc, &dio->mc, sizeof(p->mc));
|
memcpy(&p->mc, &dio->mc, sizeof(p->mc));
|
||||||
#endif /* RPL_DAG_MC != RPL_DAG_MC_NONE */
|
#endif /* RPL_WITH_MC */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -757,10 +806,14 @@ rpl_select_dag(rpl_instance_t *instance, rpl_parent_t *p)
|
||||||
|
|
||||||
instance->of->update_metric_container(instance);
|
instance->of->update_metric_container(instance);
|
||||||
/* Update the DAG rank. */
|
/* Update the DAG rank. */
|
||||||
best_dag->rank = instance->of->calculate_rank(best_dag->preferred_parent, 0);
|
best_dag->rank = rpl_rank_via_parent(best_dag->preferred_parent);
|
||||||
if(last_parent == NULL || best_dag->rank < best_dag->min_rank) {
|
if(last_parent == NULL || best_dag->rank < best_dag->min_rank) {
|
||||||
|
/* This is a slight departure from RFC6550: if we had no preferred parent before,
|
||||||
|
* reset min_rank. This helps recovering from temporary bad link conditions. */
|
||||||
best_dag->min_rank = best_dag->rank;
|
best_dag->min_rank = best_dag->rank;
|
||||||
} else if(!acceptable_rank(best_dag, best_dag->rank)) {
|
}
|
||||||
|
|
||||||
|
if(!acceptable_rank(best_dag, best_dag->rank)) {
|
||||||
PRINTF("RPL: New rank unacceptable!\n");
|
PRINTF("RPL: New rank unacceptable!\n");
|
||||||
rpl_set_preferred_parent(instance->current_dag, NULL);
|
rpl_set_preferred_parent(instance->current_dag, NULL);
|
||||||
if(instance->mop != RPL_MOP_NO_DOWNWARD_ROUTES && last_parent != NULL) {
|
if(instance->mop != RPL_MOP_NO_DOWNWARD_ROUTES && last_parent != NULL) {
|
||||||
|
@ -796,22 +849,42 @@ rpl_select_dag(rpl_instance_t *instance, rpl_parent_t *p)
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
static rpl_parent_t *
|
static rpl_parent_t *
|
||||||
best_parent(rpl_dag_t *dag)
|
best_parent(rpl_dag_t *dag, int fresh_only)
|
||||||
{
|
{
|
||||||
rpl_parent_t *p, *best;
|
rpl_parent_t *p;
|
||||||
|
rpl_of_t *of;
|
||||||
|
rpl_parent_t *best = NULL;
|
||||||
|
|
||||||
best = NULL;
|
if(dag == NULL || dag->instance == NULL || dag->instance->of == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
p = nbr_table_head(rpl_parents);
|
of = dag->instance->of;
|
||||||
while(p != NULL) {
|
/* Search for the best parent according to the OF */
|
||||||
|
for(p = nbr_table_head(rpl_parents); p != NULL; p = nbr_table_next(rpl_parents, p)) {
|
||||||
|
|
||||||
|
/* Exclude parents from other DAGs or announcing an infinite rank */
|
||||||
if(p->dag != dag || p->rank == INFINITE_RANK) {
|
if(p->dag != dag || p->rank == INFINITE_RANK) {
|
||||||
/* ignore this neighbor */
|
continue;
|
||||||
} else if(best == NULL) {
|
|
||||||
best = p;
|
|
||||||
} else {
|
|
||||||
best = dag->instance->of->best_parent(best, p);
|
|
||||||
}
|
}
|
||||||
p = nbr_table_next(rpl_parents, p);
|
|
||||||
|
if(fresh_only && !rpl_parent_is_fresh(p)) {
|
||||||
|
/* Filter out non-fresh parents if fresh_only is set */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef UIP_CONF_ND6_SEND_NA
|
||||||
|
{
|
||||||
|
uip_ds6_nbr_t *nbr = rpl_get_nbr(p);
|
||||||
|
/* Exclude links to a neighbor that is not reachable at a NUD level */
|
||||||
|
if(nbr == NULL || nbr->state != NBR_REACHABLE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* UIP_CONF_ND6_SEND_NA */
|
||||||
|
|
||||||
|
/* Now we have an acceptable parent, check if it is the new best */
|
||||||
|
best = of->best_parent(best, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
return best;
|
return best;
|
||||||
|
@ -820,16 +893,37 @@ best_parent(rpl_dag_t *dag)
|
||||||
rpl_parent_t *
|
rpl_parent_t *
|
||||||
rpl_select_parent(rpl_dag_t *dag)
|
rpl_select_parent(rpl_dag_t *dag)
|
||||||
{
|
{
|
||||||
rpl_parent_t *best = best_parent(dag);
|
/* Look for best parent (regardless of freshness) */
|
||||||
|
rpl_parent_t *best = best_parent(dag, 0);
|
||||||
|
|
||||||
if(best != NULL) {
|
if(best != NULL) {
|
||||||
rpl_set_preferred_parent(dag, best);
|
#if RPL_WITH_PROBING
|
||||||
dag->rank = dag->instance->of->calculate_rank(dag->preferred_parent, 0);
|
if(rpl_parent_is_fresh(best)) {
|
||||||
|
rpl_set_preferred_parent(dag, best);
|
||||||
|
} else {
|
||||||
|
/* The best is not fresh. Look for the best fresh now. */
|
||||||
|
rpl_parent_t *best_fresh = best_parent(dag, 1);
|
||||||
|
if(best_fresh == NULL) {
|
||||||
|
/* No fresh parent around, use best (non-fresh) */
|
||||||
|
rpl_set_preferred_parent(dag, best);
|
||||||
|
} else {
|
||||||
|
/* Use best fresh */
|
||||||
|
rpl_set_preferred_parent(dag, best_fresh);
|
||||||
|
}
|
||||||
|
/* Probe the best parent shortly in order to get a fresh estimate */
|
||||||
|
dag->instance->urgent_probing_target = best;
|
||||||
|
rpl_schedule_probing(dag->instance);
|
||||||
|
#else /* RPL_WITH_PROBING */
|
||||||
|
rpl_set_preferred_parent(dag, best);
|
||||||
|
dag->rank = rpl_rank_via_parent(dag->preferred_parent);
|
||||||
|
#endif /* RPL_WITH_PROBING */
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dag->rank = INFINITE_RANK;
|
rpl_set_preferred_parent(dag, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
return best;
|
dag->rank = rpl_rank_via_parent(dag->preferred_parent);
|
||||||
|
return dag->preferred_parent;
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
void
|
void
|
||||||
|
@ -1003,6 +1097,10 @@ rpl_join_instance(uip_ipaddr_t *from, rpl_dio_t *dio)
|
||||||
|
|
||||||
instance->of = of;
|
instance->of = of;
|
||||||
instance->mop = dio->mop;
|
instance->mop = dio->mop;
|
||||||
|
instance->mc.type = dio->mc.type;
|
||||||
|
instance->mc.flags = dio->mc.flags;
|
||||||
|
instance->mc.aggr = dio->mc.aggr;
|
||||||
|
instance->mc.prec = dio->mc.prec;
|
||||||
instance->current_dag = dag;
|
instance->current_dag = dag;
|
||||||
instance->dtsn_out = RPL_LOLLIPOP_INIT;
|
instance->dtsn_out = RPL_LOLLIPOP_INIT;
|
||||||
|
|
||||||
|
@ -1022,7 +1120,7 @@ rpl_join_instance(uip_ipaddr_t *from, rpl_dio_t *dio)
|
||||||
|
|
||||||
rpl_set_preferred_parent(dag, p);
|
rpl_set_preferred_parent(dag, p);
|
||||||
instance->of->update_metric_container(instance);
|
instance->of->update_metric_container(instance);
|
||||||
dag->rank = instance->of->calculate_rank(p, 0);
|
dag->rank = rpl_rank_via_parent(p);
|
||||||
/* So far this is the lowest rank we are aware of. */
|
/* So far this is the lowest rank we are aware of. */
|
||||||
dag->min_rank = dag->rank;
|
dag->min_rank = dag->rank;
|
||||||
|
|
||||||
|
@ -1045,6 +1143,8 @@ rpl_join_instance(uip_ipaddr_t *from, rpl_dio_t *dio)
|
||||||
} else {
|
} else {
|
||||||
PRINTF("RPL: The DIO does not meet the prerequisites for sending a DAO\n");
|
PRINTF("RPL: The DIO does not meet the prerequisites for sending a DAO\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instance->of->reset(dag);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if RPL_MAX_DAG_PER_INSTANCE > 1
|
#if RPL_MAX_DAG_PER_INSTANCE > 1
|
||||||
|
@ -1115,7 +1215,7 @@ rpl_add_dag(uip_ipaddr_t *from, rpl_dio_t *dio)
|
||||||
memcpy(&dag->prefix_info, &dio->prefix_info, sizeof(rpl_prefix_t));
|
memcpy(&dag->prefix_info, &dio->prefix_info, sizeof(rpl_prefix_t));
|
||||||
|
|
||||||
rpl_set_preferred_parent(dag, p);
|
rpl_set_preferred_parent(dag, p);
|
||||||
dag->rank = instance->of->calculate_rank(p, 0);
|
dag->rank = rpl_rank_via_parent(p);
|
||||||
dag->min_rank = dag->rank; /* So far this is the lowest rank we know of. */
|
dag->min_rank = dag->rank; /* So far this is the lowest rank we know of. */
|
||||||
|
|
||||||
PRINTF("RPL: Joined DAG with instance ID %u, rank %hu, DAG ID ",
|
PRINTF("RPL: Joined DAG with instance ID %u, rank %hu, DAG ID ",
|
||||||
|
@ -1157,7 +1257,7 @@ global_repair(uip_ipaddr_t *from, rpl_dag_t *dag, rpl_dio_t *dio)
|
||||||
PRINTF("RPL: Failed to add a parent during the global repair\n");
|
PRINTF("RPL: Failed to add a parent during the global repair\n");
|
||||||
dag->rank = INFINITE_RANK;
|
dag->rank = INFINITE_RANK;
|
||||||
} else {
|
} else {
|
||||||
dag->rank = dag->instance->of->calculate_rank(p, 0);
|
dag->rank = rpl_rank_via_parent(p);
|
||||||
dag->min_rank = dag->rank;
|
dag->min_rank = dag->rank;
|
||||||
PRINTF("RPL: rpl_process_parent_event global repair\n");
|
PRINTF("RPL: rpl_process_parent_event global repair\n");
|
||||||
rpl_process_parent_event(dag->instance, p);
|
rpl_process_parent_event(dag->instance, p);
|
||||||
|
@ -1224,6 +1324,7 @@ int
|
||||||
rpl_process_parent_event(rpl_instance_t *instance, rpl_parent_t *p)
|
rpl_process_parent_event(rpl_instance_t *instance, rpl_parent_t *p)
|
||||||
{
|
{
|
||||||
int return_value;
|
int return_value;
|
||||||
|
rpl_parent_t *last_parent = instance->current_dag->preferred_parent;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
rpl_rank_t old_rank;
|
rpl_rank_t old_rank;
|
||||||
|
@ -1232,10 +1333,18 @@ rpl_process_parent_event(rpl_instance_t *instance, rpl_parent_t *p)
|
||||||
|
|
||||||
return_value = 1;
|
return_value = 1;
|
||||||
|
|
||||||
|
if(uip_ds6_route_is_nexthop(rpl_get_parent_ipaddr(p)) && !rpl_parent_is_reachable(p)) {
|
||||||
|
PRINTF("RPL: Unacceptable link %u, removing routes via: ", rpl_get_parent_link_metric(p));
|
||||||
|
PRINT6ADDR(rpl_get_parent_ipaddr(p));
|
||||||
|
PRINTF("\n");
|
||||||
|
rpl_remove_routes_by_nexthop(rpl_get_parent_ipaddr(p), p->dag);
|
||||||
|
}
|
||||||
|
|
||||||
if(!acceptable_rank(p->dag, p->rank)) {
|
if(!acceptable_rank(p->dag, p->rank)) {
|
||||||
/* The candidate parent is no longer valid: the rank increase resulting
|
/* The candidate parent is no longer valid: the rank increase resulting
|
||||||
from the choice of it as a parent would be too high. */
|
from the choice of it as a parent would be too high. */
|
||||||
PRINTF("RPL: Unacceptable rank %u\n", (unsigned)p->rank);
|
PRINTF("RPL: Unacceptable rank %u (Current min %u, MaxRankInc %u)\n", (unsigned)p->rank,
|
||||||
|
p->dag->min_rank, p->dag->instance->max_rankinc);
|
||||||
rpl_nullify_parent(p);
|
rpl_nullify_parent(p);
|
||||||
if(p != instance->current_dag->preferred_parent) {
|
if(p != instance->current_dag->preferred_parent) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1245,10 +1354,12 @@ rpl_process_parent_event(rpl_instance_t *instance, rpl_parent_t *p)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(rpl_select_dag(instance, p) == NULL) {
|
if(rpl_select_dag(instance, p) == NULL) {
|
||||||
/* No suitable parent; trigger a local repair. */
|
if(last_parent != NULL) {
|
||||||
PRINTF("RPL: No parents found in any DAG\n");
|
/* No suitable parent anymore; trigger a local repair. */
|
||||||
rpl_local_repair(instance);
|
PRINTF("RPL: No parents found in any DAG\n");
|
||||||
return 0;
|
rpl_local_repair(instance);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
@ -1371,8 +1482,6 @@ rpl_process_dio(uip_ipaddr_t *from, rpl_dio_t *dio)
|
||||||
PRINTF("RPL: Ignoring DIO with too low rank: %u\n",
|
PRINTF("RPL: Ignoring DIO with too low rank: %u\n",
|
||||||
(unsigned)dio->rank);
|
(unsigned)dio->rank);
|
||||||
return;
|
return;
|
||||||
} else if(dio->rank == INFINITE_RANK && dag->joined) {
|
|
||||||
rpl_reset_dio_timer(instance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Prefix Information Option treated to add new prefix */
|
/* Prefix Information Option treated to add new prefix */
|
||||||
|
@ -1439,6 +1548,11 @@ rpl_process_dio(uip_ipaddr_t *from, rpl_dio_t *dio)
|
||||||
}
|
}
|
||||||
p->rank = dio->rank;
|
p->rank = dio->rank;
|
||||||
|
|
||||||
|
if(dio->rank == INFINITE_RANK && p == dag->preferred_parent) {
|
||||||
|
/* Our preferred parent advertised an infinite rank, reset DIO timer */
|
||||||
|
rpl_reset_dio_timer(instance);
|
||||||
|
}
|
||||||
|
|
||||||
/* Parent info has been updated, trigger rank recalculation */
|
/* Parent info has been updated, trigger rank recalculation */
|
||||||
p->flags |= RPL_PARENT_FLAG_UPDATED;
|
p->flags |= RPL_PARENT_FLAG_UPDATED;
|
||||||
|
|
||||||
|
@ -1446,14 +1560,14 @@ rpl_process_dio(uip_ipaddr_t *from, rpl_dio_t *dio)
|
||||||
PRINT6ADDR(&instance->current_dag->dag_id);
|
PRINT6ADDR(&instance->current_dag->dag_id);
|
||||||
PRINTF(", rank %u, min_rank %u, ",
|
PRINTF(", rank %u, min_rank %u, ",
|
||||||
instance->current_dag->rank, instance->current_dag->min_rank);
|
instance->current_dag->rank, instance->current_dag->min_rank);
|
||||||
PRINTF("parent rank %u, parent etx %u, link metric %u, instance etx %u\n",
|
PRINTF("parent rank %u, link metric %u\n",
|
||||||
p->rank, -1/*p->mc.obj.etx*/, rpl_get_nbr(p)->link_metric, instance->mc.obj.etx);
|
p->rank, rpl_get_parent_link_metric(p));
|
||||||
|
|
||||||
/* We have allocated a candidate parent; process the DIO further. */
|
/* We have allocated a candidate parent; process the DIO further. */
|
||||||
|
|
||||||
#if RPL_DAG_MC != RPL_DAG_MC_NONE
|
#if RPL_WITH_MC
|
||||||
memcpy(&p->mc, &dio->mc, sizeof(p->mc));
|
memcpy(&p->mc, &dio->mc, sizeof(p->mc));
|
||||||
#endif /* RPL_DAG_MC != RPL_DAG_MC_NONE */
|
#endif /* RPL_WITH_MC */
|
||||||
if(rpl_process_parent_event(instance, p) == 0) {
|
if(rpl_process_parent_event(instance, p) == 0) {
|
||||||
PRINTF("RPL: The candidate parent is rejected\n");
|
PRINTF("RPL: The candidate parent is rejected\n");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -125,14 +125,6 @@ rpl_verify_header(int uip_ext_opt_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
sender_rank = UIP_HTONS(UIP_EXT_HDR_OPT_RPL_BUF->senderrank);
|
sender_rank = UIP_HTONS(UIP_EXT_HDR_OPT_RPL_BUF->senderrank);
|
||||||
sender_closer = sender_rank < instance->current_dag->rank;
|
|
||||||
|
|
||||||
PRINTF("RPL: Packet going %s, sender closer %d (%d < %d)\n", down == 1 ? "down" : "up",
|
|
||||||
sender_closer,
|
|
||||||
sender_rank,
|
|
||||||
instance->current_dag->rank
|
|
||||||
);
|
|
||||||
|
|
||||||
sender = nbr_table_get_from_lladdr(rpl_parents, packetbuf_addr(PACKETBUF_ADDR_SENDER));
|
sender = nbr_table_get_from_lladdr(rpl_parents, packetbuf_addr(PACKETBUF_ADDR_SENDER));
|
||||||
|
|
||||||
if(sender != NULL && (UIP_EXT_HDR_OPT_RPL_BUF->flags & RPL_HDR_OPT_RANK_ERR)) {
|
if(sender != NULL && (UIP_EXT_HDR_OPT_RPL_BUF->flags & RPL_HDR_OPT_RANK_ERR)) {
|
||||||
|
@ -142,6 +134,14 @@ rpl_verify_header(int uip_ext_opt_offset)
|
||||||
rpl_select_dag(instance, sender);
|
rpl_select_dag(instance, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sender_closer = sender_rank < instance->current_dag->rank;
|
||||||
|
|
||||||
|
PRINTF("RPL: Packet going %s, sender closer %d (%d < %d)\n", down == 1 ? "down" : "up",
|
||||||
|
sender_closer,
|
||||||
|
sender_rank,
|
||||||
|
instance->current_dag->rank
|
||||||
|
);
|
||||||
|
|
||||||
if((down && !sender_closer) || (!down && sender_closer)) {
|
if((down && !sender_closer) || (!down && sender_closer)) {
|
||||||
PRINTF("RPL: Loop detected - senderrank: %d my-rank: %d sender_closer: %d\n",
|
PRINTF("RPL: Loop detected - senderrank: %d my-rank: %d sender_closer: %d\n",
|
||||||
sender_rank, instance->current_dag->rank,
|
sender_rank, instance->current_dag->rank,
|
||||||
|
|
|
@ -91,8 +91,6 @@ void RPL_DEBUG_DAO_OUTPUT(rpl_parent_t *);
|
||||||
|
|
||||||
static uint8_t dao_sequence = RPL_LOLLIPOP_INIT;
|
static uint8_t dao_sequence = RPL_LOLLIPOP_INIT;
|
||||||
|
|
||||||
extern rpl_of_t RPL_OF;
|
|
||||||
|
|
||||||
#if RPL_CONF_MULTICAST
|
#if RPL_CONF_MULTICAST
|
||||||
static uip_mcast6_route_t *mcast_group;
|
static uip_mcast6_route_t *mcast_group;
|
||||||
#endif
|
#endif
|
||||||
|
@ -302,7 +300,7 @@ dio_input(void)
|
||||||
dio.dag_redund = RPL_DIO_REDUNDANCY;
|
dio.dag_redund = RPL_DIO_REDUNDANCY;
|
||||||
dio.dag_min_hoprankinc = RPL_MIN_HOPRANKINC;
|
dio.dag_min_hoprankinc = RPL_MIN_HOPRANKINC;
|
||||||
dio.dag_max_rankinc = RPL_MAX_RANKINC;
|
dio.dag_max_rankinc = RPL_MAX_RANKINC;
|
||||||
dio.ocp = RPL_OF.ocp;
|
dio.ocp = RPL_OF_OCP;
|
||||||
dio.default_lifetime = RPL_DEFAULT_LIFETIME;
|
dio.default_lifetime = RPL_DEFAULT_LIFETIME;
|
||||||
dio.lifetime_unit = RPL_DEFAULT_LIFETIME_UNIT;
|
dio.lifetime_unit = RPL_DEFAULT_LIFETIME_UNIT;
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \file
|
* \file
|
||||||
* The Minimum Rank with Hysteresis Objective Function (MRHOF)
|
* The Minimum Rank with Hysteresis Objective Function (MRHOF), RFC6719
|
||||||
*
|
*
|
||||||
* This implementation uses the estimated number of
|
* This implementation uses the estimated number of
|
||||||
* transmissions (ETX) as the additive routing metric,
|
* transmissions (ETX) as the additive routing metric,
|
||||||
|
@ -46,83 +46,55 @@
|
||||||
* @{
|
* @{
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "net/rpl/rpl.h"
|
||||||
#include "net/rpl/rpl-private.h"
|
#include "net/rpl/rpl-private.h"
|
||||||
#include "net/nbr-table.h"
|
#include "net/nbr-table.h"
|
||||||
|
#include "net/link-stats.h"
|
||||||
|
|
||||||
#define DEBUG DEBUG_NONE
|
#define DEBUG DEBUG_NONE
|
||||||
#include "net/ip/uip-debug.h"
|
#include "net/ip/uip-debug.h"
|
||||||
|
|
||||||
static void reset(rpl_dag_t *);
|
/* RFC6551 and RFC6719 do not mandate the use of a specific formula to
|
||||||
static void neighbor_link_callback(rpl_parent_t *, int, int);
|
* compute the ETX value. This MRHOF implementation relies on the value
|
||||||
#if RPL_WITH_DAO_ACK
|
* computed by the link-stats module. It has an optional feature,
|
||||||
static void dao_ack_callback(rpl_parent_t *, int);
|
* RPL_MRHOF_CONF_SQUARED_ETX, that consists in squaring this value.
|
||||||
#endif
|
* This basically penalizes bad links while preserving the semantics of ETX
|
||||||
static rpl_parent_t *best_parent(rpl_parent_t *, rpl_parent_t *);
|
* (1 = perfect link, more = worse link). As a result, MRHOF will favor
|
||||||
static rpl_dag_t *best_dag(rpl_dag_t *, rpl_dag_t *);
|
* good links over short paths. Recommended when reliability is a priority.
|
||||||
static rpl_rank_t calculate_rank(rpl_parent_t *, rpl_rank_t);
|
* Without this feature, a hop with 50% PRR (ETX=2) is equivalent to two
|
||||||
static void update_metric_container(rpl_instance_t *);
|
* perfect hops with 100% PRR (ETX=1+1=2). With this feature, the former
|
||||||
|
* path obtains ETX=2*2=4 and the former ETX=1*1+1*1=2. */
|
||||||
|
#ifdef RPL_MRHOF_CONF_SQUARED_ETX
|
||||||
|
#define RPL_MRHOF_SQUARED_ETX RPL_MRHOF_CONF_SQUARED_ETX
|
||||||
|
#else /* RPL_MRHOF_CONF_SQUARED_ETX */
|
||||||
|
#define RPL_MRHOF_SQUARED_ETX 0
|
||||||
|
#endif /* RPL_MRHOF_CONF_SQUARED_ETX */
|
||||||
|
|
||||||
rpl_of_t rpl_mrhof = {
|
#if !RPL_MRHOF_SQUARED_ETX
|
||||||
reset,
|
/* Configuration parameters of RFC6719. Reject parents that have a higher
|
||||||
neighbor_link_callback,
|
* link metric than the following. The default value is 512 but we use 1024. */
|
||||||
#if RPL_WITH_DAO_ACK
|
#define MAX_LINK_METRIC 1024 /* Eq ETX of 8 */
|
||||||
dao_ack_callback,
|
/* Hysteresis of MRHOF: the rank must differ more than PARENT_SWITCH_THRESHOLD_DIV
|
||||||
#endif
|
* in order to switch preferred parent. Default in RFC6719: 192, eq ETX of 1.5.
|
||||||
best_parent,
|
* We use a more aggressive setting: 96, eq ETX of 0.75.
|
||||||
best_dag,
|
*/
|
||||||
calculate_rank,
|
#define PARENT_SWITCH_THRESHOLD 96 /* Eq ETX of 0.75 */
|
||||||
update_metric_container,
|
#else /* !RPL_MRHOF_SQUARED_ETX */
|
||||||
1
|
#define MAX_LINK_METRIC 2048 /* Eq ETX of 4 */
|
||||||
};
|
#define PARENT_SWITCH_THRESHOLD 160 /* Eq ETX of 1.25 (results in a churn comparable
|
||||||
|
to the threshold of 96 in the non-squared case) */
|
||||||
/* Constants for the ETX moving average */
|
#endif /* !RPL_MRHOF_SQUARED_ETX */
|
||||||
#define ETX_SCALE 100
|
|
||||||
#define ETX_ALPHA 90
|
|
||||||
|
|
||||||
/* Reject parents that have a higher link metric than the following. */
|
|
||||||
#define MAX_LINK_METRIC 10
|
|
||||||
|
|
||||||
/* Reject parents that have a higher path cost than the following. */
|
/* Reject parents that have a higher path cost than the following. */
|
||||||
#define MAX_PATH_COST 100
|
#define MAX_PATH_COST 32768 /* Eq path ETX of 256 */
|
||||||
|
|
||||||
/*
|
|
||||||
* The rank must differ more than 1/PARENT_SWITCH_THRESHOLD_DIV in order
|
|
||||||
* to switch preferred parent.
|
|
||||||
*/
|
|
||||||
#define PARENT_SWITCH_THRESHOLD_DIV 2
|
|
||||||
|
|
||||||
typedef uint16_t rpl_path_metric_t;
|
|
||||||
|
|
||||||
static rpl_path_metric_t
|
|
||||||
calculate_path_metric(rpl_parent_t *p)
|
|
||||||
{
|
|
||||||
uip_ds6_nbr_t *nbr;
|
|
||||||
if(p == NULL) {
|
|
||||||
return MAX_PATH_COST * RPL_DAG_MC_ETX_DIVISOR;
|
|
||||||
}
|
|
||||||
nbr = rpl_get_nbr(p);
|
|
||||||
if(nbr == NULL) {
|
|
||||||
return MAX_PATH_COST * RPL_DAG_MC_ETX_DIVISOR;
|
|
||||||
}
|
|
||||||
#if RPL_DAG_MC == RPL_DAG_MC_NONE
|
|
||||||
{
|
|
||||||
return p->rank + (uint16_t)nbr->link_metric;
|
|
||||||
}
|
|
||||||
#elif RPL_DAG_MC == RPL_DAG_MC_ETX
|
|
||||||
return p->mc.obj.etx + (uint16_t)nbr->link_metric;
|
|
||||||
#elif RPL_DAG_MC == RPL_DAG_MC_ENERGY
|
|
||||||
return p->mc.obj.energy.energy_est + (uint16_t)nbr->link_metric;
|
|
||||||
#else
|
|
||||||
#error "Unsupported RPL_DAG_MC configured. See rpl.h."
|
|
||||||
#endif /* RPL_DAG_MC */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static void
|
static void
|
||||||
reset(rpl_dag_t *dag)
|
reset(rpl_dag_t *dag)
|
||||||
{
|
{
|
||||||
PRINTF("RPL: Reset MRHOF\n");
|
PRINTF("RPL: Reset MRHOF\n");
|
||||||
}
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
#if RPL_WITH_DAO_ACK
|
#if RPL_WITH_DAO_ACK
|
||||||
static void
|
static void
|
||||||
dao_ack_callback(rpl_parent_t *p, int status)
|
dao_ack_callback(rpl_parent_t *p, int status)
|
||||||
|
@ -134,87 +106,127 @@ dao_ack_callback(rpl_parent_t *p, int status)
|
||||||
PRINTF("RPL: MRHOF - DAO ACK received with status: %d\n", status);
|
PRINTF("RPL: MRHOF - DAO ACK received with status: %d\n", status);
|
||||||
if(status >= RPL_DAO_ACK_UNABLE_TO_ACCEPT) {
|
if(status >= RPL_DAO_ACK_UNABLE_TO_ACCEPT) {
|
||||||
/* punish the ETX as if this was 10 packets lost */
|
/* punish the ETX as if this was 10 packets lost */
|
||||||
neighbor_link_callback(p, MAC_TX_OK, 10);
|
link_stats_packet_sent(rpl_get_parent_lladdr(p), MAC_TX_OK, 10);
|
||||||
} else if(status == RPL_DAO_ACK_TIMEOUT) { /* timeout = no ack */
|
} else if(status == RPL_DAO_ACK_TIMEOUT) { /* timeout = no ack */
|
||||||
/* punish the total lack of ACK with a similar punishment */
|
/* punish the total lack of ACK with a similar punishment */
|
||||||
neighbor_link_callback(p, MAC_TX_OK, 10);
|
link_stats_packet_sent(rpl_get_parent_lladdr(p), MAC_TX_OK, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif /* RPL_WITH_DAO_ACK */
|
#endif /* RPL_WITH_DAO_ACK */
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static void
|
static uint16_t
|
||||||
neighbor_link_callback(rpl_parent_t *p, int status, int numtx)
|
parent_link_metric(rpl_parent_t *p)
|
||||||
{
|
{
|
||||||
uint16_t recorded_etx = 0;
|
const struct link_stats *stats = rpl_get_parent_link_stats(p);
|
||||||
uint16_t packet_etx = numtx * RPL_DAG_MC_ETX_DIVISOR;
|
if(stats != NULL) {
|
||||||
uint16_t new_etx;
|
#if RPL_MRHOF_SQUARED_ETX
|
||||||
uip_ds6_nbr_t *nbr = NULL;
|
uint32_t squared_etx = ((uint32_t)stats->etx * stats->etx) / LINK_STATS_ETX_DIVISOR;
|
||||||
|
return (uint16_t)MIN(squared_etx, 0xffff);
|
||||||
nbr = rpl_get_nbr(p);
|
#else /* RPL_MRHOF_SQUARED_ETX */
|
||||||
if(nbr == NULL) {
|
return stats->etx;
|
||||||
/* No neighbor for this parent - something bad has occurred */
|
#endif /* RPL_MRHOF_SQUARED_ETX */
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
recorded_etx = nbr->link_metric;
|
|
||||||
|
|
||||||
/* Do not penalize the ETX when collisions or transmission errors occur. */
|
|
||||||
if(status == MAC_TX_OK || status == MAC_TX_NOACK) {
|
|
||||||
if(status == MAC_TX_NOACK) {
|
|
||||||
packet_etx = MAX_LINK_METRIC * RPL_DAG_MC_ETX_DIVISOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(p->flags & RPL_PARENT_FLAG_LINK_METRIC_VALID) {
|
|
||||||
/* We already have a valid link metric, use weighted moving average to update it */
|
|
||||||
new_etx = ((uint32_t)recorded_etx * ETX_ALPHA +
|
|
||||||
(uint32_t)packet_etx * (ETX_SCALE - ETX_ALPHA)) / ETX_SCALE;
|
|
||||||
} else {
|
|
||||||
/* We don't have a valid link metric, set it to the current packet's ETX */
|
|
||||||
new_etx = packet_etx;
|
|
||||||
/* Set link metric as valid */
|
|
||||||
p->flags |= RPL_PARENT_FLAG_LINK_METRIC_VALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
PRINTF("RPL: ETX changed from %u to %u (packet ETX = %u)\n",
|
|
||||||
(unsigned)(recorded_etx / RPL_DAG_MC_ETX_DIVISOR),
|
|
||||||
(unsigned)(new_etx / RPL_DAG_MC_ETX_DIVISOR),
|
|
||||||
(unsigned)(packet_etx / RPL_DAG_MC_ETX_DIVISOR));
|
|
||||||
/* update the link metric for this nbr */
|
|
||||||
nbr->link_metric = new_etx;
|
|
||||||
}
|
}
|
||||||
|
return 0xffff;
|
||||||
}
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static uint16_t
|
||||||
|
parent_path_cost(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
uint16_t base;
|
||||||
|
|
||||||
|
if(p == NULL || p->dag == NULL || p->dag->instance == NULL) {
|
||||||
|
return 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if RPL_WITH_MC
|
||||||
|
/* Handle the different MC types */
|
||||||
|
switch(p->dag->instance->mc.type) {
|
||||||
|
case RPL_DAG_MC_ETX:
|
||||||
|
base = p->mc.obj.etx;
|
||||||
|
break;
|
||||||
|
case RPL_DAG_MC_ENERGY:
|
||||||
|
base = p->mc.obj.energy.energy_est << 8;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
base = p->rank;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#else /* RPL_WITH_MC */
|
||||||
|
base = p->rank;
|
||||||
|
#endif /* RPL_WITH_MC */
|
||||||
|
|
||||||
|
/* path cost upper bound: 0xffff */
|
||||||
|
return MIN((uint32_t)base + parent_link_metric(p), 0xffff);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static rpl_rank_t
|
static rpl_rank_t
|
||||||
calculate_rank(rpl_parent_t *p, rpl_rank_t base_rank)
|
rank_via_parent(rpl_parent_t *p)
|
||||||
{
|
{
|
||||||
rpl_rank_t new_rank;
|
uint16_t min_hoprankinc;
|
||||||
rpl_rank_t rank_increase;
|
uint16_t path_cost;
|
||||||
uip_ds6_nbr_t *nbr;
|
|
||||||
|
|
||||||
if(p == NULL || (nbr = rpl_get_nbr(p)) == NULL) {
|
if(p == NULL || p->dag == NULL || p->dag->instance == NULL) {
|
||||||
if(base_rank == 0) {
|
return INFINITE_RANK;
|
||||||
return INFINITE_RANK;
|
|
||||||
}
|
|
||||||
rank_increase = RPL_INIT_LINK_METRIC * RPL_DAG_MC_ETX_DIVISOR;
|
|
||||||
} else {
|
|
||||||
rank_increase = nbr->link_metric;
|
|
||||||
if(base_rank == 0) {
|
|
||||||
base_rank = p->rank;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(INFINITE_RANK - base_rank < rank_increase) {
|
min_hoprankinc = p->dag->instance->min_hoprankinc;
|
||||||
/* Reached the maximum rank. */
|
path_cost = parent_path_cost(p);
|
||||||
new_rank = INFINITE_RANK;
|
|
||||||
} else {
|
|
||||||
/* Calculate the rank based on the new rank information from DIO or
|
|
||||||
stored otherwise. */
|
|
||||||
new_rank = base_rank + rank_increase;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new_rank;
|
/* Rank lower-bound: parent rank + min_hoprankinc */
|
||||||
|
return MAX(MIN((uint32_t)p->rank + min_hoprankinc, 0xffff), path_cost);
|
||||||
}
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static int
|
||||||
|
parent_is_acceptable(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
uint16_t link_metric = parent_link_metric(p);
|
||||||
|
uint16_t path_cost = parent_path_cost(p);
|
||||||
|
/* Exclude links with too high link metrics or path cost (RFC6719, 3.2.2) */
|
||||||
|
return link_metric <= MAX_LINK_METRIC && path_cost <= MAX_PATH_COST;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static int
|
||||||
|
parent_has_usable_link(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
uint16_t link_metric = parent_link_metric(p);
|
||||||
|
/* Exclude links with too high link metrics */
|
||||||
|
return link_metric <= MAX_LINK_METRIC;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static rpl_parent_t *
|
||||||
|
best_parent(rpl_parent_t *p1, rpl_parent_t *p2)
|
||||||
|
{
|
||||||
|
rpl_dag_t *dag;
|
||||||
|
uint16_t p1_cost;
|
||||||
|
uint16_t p2_cost;
|
||||||
|
int p1_is_acceptable;
|
||||||
|
int p2_is_acceptable;
|
||||||
|
|
||||||
|
p1_is_acceptable = p1 != NULL && parent_is_acceptable(p1);
|
||||||
|
p2_is_acceptable = p2 != NULL && parent_is_acceptable(p2);
|
||||||
|
|
||||||
|
if(!p1_is_acceptable) {
|
||||||
|
return p2_is_acceptable ? p2 : NULL;
|
||||||
|
}
|
||||||
|
if(!p2_is_acceptable) {
|
||||||
|
return p1_is_acceptable ? p1 : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
dag = p1->dag; /* Both parents are in the same DAG. */
|
||||||
|
p1_cost = parent_path_cost(p1);
|
||||||
|
p2_cost = parent_path_cost(p2);
|
||||||
|
|
||||||
|
/* Maintain stability of the preferred parent in case of similar ranks. */
|
||||||
|
if(p1 == dag->preferred_parent || p2 == dag->preferred_parent) {
|
||||||
|
if(p1_cost < p2_cost + PARENT_SWITCH_THRESHOLD &&
|
||||||
|
p1_cost > p2_cost - PARENT_SWITCH_THRESHOLD) {
|
||||||
|
return dag->preferred_parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p1_cost < p2_cost ? p1 : p2;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static rpl_dag_t *
|
static rpl_dag_t *
|
||||||
best_dag(rpl_dag_t *d1, rpl_dag_t *d2)
|
best_dag(rpl_dag_t *d1, rpl_dag_t *d2)
|
||||||
{
|
{
|
||||||
|
@ -228,93 +240,77 @@ best_dag(rpl_dag_t *d1, rpl_dag_t *d2)
|
||||||
|
|
||||||
return d1->rank < d2->rank ? d1 : d2;
|
return d1->rank < d2->rank ? d1 : d2;
|
||||||
}
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static rpl_parent_t *
|
#if !RPL_WITH_MC
|
||||||
best_parent(rpl_parent_t *p1, rpl_parent_t *p2)
|
|
||||||
{
|
|
||||||
rpl_dag_t *dag;
|
|
||||||
rpl_path_metric_t min_diff;
|
|
||||||
rpl_path_metric_t p1_metric;
|
|
||||||
rpl_path_metric_t p2_metric;
|
|
||||||
|
|
||||||
dag = p1->dag; /* Both parents are in the same DAG. */
|
|
||||||
|
|
||||||
min_diff = RPL_DAG_MC_ETX_DIVISOR /
|
|
||||||
PARENT_SWITCH_THRESHOLD_DIV;
|
|
||||||
|
|
||||||
p1_metric = calculate_path_metric(p1);
|
|
||||||
p2_metric = calculate_path_metric(p2);
|
|
||||||
|
|
||||||
/* Maintain stability of the preferred parent in case of similar ranks. */
|
|
||||||
if(p1 == dag->preferred_parent || p2 == dag->preferred_parent) {
|
|
||||||
if(p1_metric < p2_metric + min_diff &&
|
|
||||||
p1_metric > p2_metric - min_diff) {
|
|
||||||
PRINTF("RPL: MRHOF hysteresis: %u <= %u <= %u\n",
|
|
||||||
p2_metric - min_diff,
|
|
||||||
p1_metric,
|
|
||||||
p2_metric + min_diff);
|
|
||||||
return dag->preferred_parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p1_metric < p2_metric ? p1 : p2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if RPL_DAG_MC == RPL_DAG_MC_NONE
|
|
||||||
static void
|
static void
|
||||||
update_metric_container(rpl_instance_t *instance)
|
update_metric_container(rpl_instance_t *instance)
|
||||||
{
|
{
|
||||||
instance->mc.type = RPL_DAG_MC;
|
instance->mc.type = RPL_DAG_MC_NONE;
|
||||||
}
|
}
|
||||||
#else
|
#else /* RPL_WITH_MC */
|
||||||
static void
|
static void
|
||||||
update_metric_container(rpl_instance_t *instance)
|
update_metric_container(rpl_instance_t *instance)
|
||||||
{
|
{
|
||||||
rpl_path_metric_t path_metric;
|
|
||||||
rpl_dag_t *dag;
|
rpl_dag_t *dag;
|
||||||
#if RPL_DAG_MC == RPL_DAG_MC_ENERGY
|
uint16_t path_cost;
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
#endif
|
|
||||||
|
|
||||||
instance->mc.type = RPL_DAG_MC;
|
|
||||||
instance->mc.flags = RPL_DAG_MC_FLAG_P;
|
|
||||||
instance->mc.aggr = RPL_DAG_MC_AGGR_ADDITIVE;
|
|
||||||
instance->mc.prec = 0;
|
|
||||||
|
|
||||||
dag = instance->current_dag;
|
dag = instance->current_dag;
|
||||||
|
if(dag == NULL || !dag->joined) {
|
||||||
if (!dag->joined) {
|
|
||||||
PRINTF("RPL: Cannot update the metric container when not joined\n");
|
PRINTF("RPL: Cannot update the metric container when not joined\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dag->rank == ROOT_RANK(instance)) {
|
if(dag->rank == ROOT_RANK(instance)) {
|
||||||
path_metric = 0;
|
/* Configure MC at root only, other nodes are auto-configured when joining */
|
||||||
|
instance->mc.type = RPL_DAG_MC;
|
||||||
|
instance->mc.flags = 0;
|
||||||
|
instance->mc.aggr = RPL_DAG_MC_AGGR_ADDITIVE;
|
||||||
|
instance->mc.prec = 0;
|
||||||
|
path_cost = dag->rank;
|
||||||
} else {
|
} else {
|
||||||
path_metric = calculate_path_metric(dag->preferred_parent);
|
path_cost = parent_path_cost(dag->preferred_parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if RPL_DAG_MC == RPL_DAG_MC_ETX
|
/* Handle the different MC types */
|
||||||
instance->mc.length = sizeof(instance->mc.obj.etx);
|
switch(instance->mc.type) {
|
||||||
instance->mc.obj.etx = path_metric;
|
case RPL_DAG_MC_NONE:
|
||||||
|
break;
|
||||||
PRINTF("RPL: My path ETX to the root is %u.%u\n",
|
case RPL_DAG_MC_ETX:
|
||||||
instance->mc.obj.etx / RPL_DAG_MC_ETX_DIVISOR,
|
instance->mc.length = sizeof(instance->mc.obj.etx);
|
||||||
(instance->mc.obj.etx % RPL_DAG_MC_ETX_DIVISOR * 100) /
|
instance->mc.obj.etx = path_cost;
|
||||||
RPL_DAG_MC_ETX_DIVISOR);
|
break;
|
||||||
#elif RPL_DAG_MC == RPL_DAG_MC_ENERGY
|
case RPL_DAG_MC_ENERGY:
|
||||||
instance->mc.length = sizeof(instance->mc.obj.energy);
|
instance->mc.length = sizeof(instance->mc.obj.energy);
|
||||||
|
if(dag->rank == ROOT_RANK(instance)) {
|
||||||
if(dag->rank == ROOT_RANK(instance)) {
|
type = RPL_DAG_MC_ENERGY_TYPE_MAINS;
|
||||||
type = RPL_DAG_MC_ENERGY_TYPE_MAINS;
|
} else {
|
||||||
} else {
|
type = RPL_DAG_MC_ENERGY_TYPE_BATTERY;
|
||||||
type = RPL_DAG_MC_ENERGY_TYPE_BATTERY;
|
}
|
||||||
|
instance->mc.obj.energy.flags = type << RPL_DAG_MC_ENERGY_TYPE;
|
||||||
|
/* Energy_est is only one byte, use the least significant byte of the path metric. */
|
||||||
|
instance->mc.obj.energy.energy_est = path_cost >> 8;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
PRINTF("RPL: MRHOF, non-supported MC %u\n", instance->mc.type);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
instance->mc.obj.energy.flags = type << RPL_DAG_MC_ENERGY_TYPE;
|
|
||||||
instance->mc.obj.energy.energy_est = path_metric;
|
|
||||||
#endif /* RPL_DAG_MC == RPL_DAG_MC_ETX */
|
|
||||||
}
|
}
|
||||||
#endif /* RPL_DAG_MC == RPL_DAG_MC_NONE */
|
#endif /* RPL_WITH_MC */
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
rpl_of_t rpl_mrhof = {
|
||||||
|
reset,
|
||||||
|
#if RPL_WITH_DAO_ACK
|
||||||
|
dao_ack_callback,
|
||||||
|
#endif
|
||||||
|
parent_link_metric,
|
||||||
|
parent_has_usable_link,
|
||||||
|
parent_path_cost,
|
||||||
|
rank_via_parent,
|
||||||
|
best_parent,
|
||||||
|
best_dag,
|
||||||
|
update_metric_container,
|
||||||
|
RPL_OCP_MRHOF
|
||||||
|
};
|
||||||
|
|
||||||
/** @}*/
|
/** @}*/
|
||||||
|
|
|
@ -138,7 +138,7 @@ update_nbr(void)
|
||||||
parent->rank > 0 &&
|
parent->rank > 0 &&
|
||||||
parent->dag != NULL &&
|
parent->dag != NULL &&
|
||||||
parent->dag->instance != NULL &&
|
parent->dag->instance != NULL &&
|
||||||
(rank = parent->dag->instance->of->calculate_rank(parent, 0)) > worst_rank) {
|
(rank = parent->dag->instance->of->rank_via_parent(parent)) > worst_rank) {
|
||||||
/* This is the worst-rank neighbor - this is a good candidate for removal */
|
/* This is the worst-rank neighbor - this is a good candidate for removal */
|
||||||
worst_rank = rank;
|
worst_rank = rank;
|
||||||
worst_rank_nbr = lladdr;
|
worst_rank_nbr = lladdr;
|
||||||
|
@ -190,7 +190,6 @@ const linkaddr_t *
|
||||||
find_removable_dio(uip_ipaddr_t *from, rpl_dio_t *dio)
|
find_removable_dio(uip_ipaddr_t *from, rpl_dio_t *dio)
|
||||||
{
|
{
|
||||||
rpl_instance_t *instance;
|
rpl_instance_t *instance;
|
||||||
rpl_rank_t rank;
|
|
||||||
|
|
||||||
update_nbr();
|
update_nbr();
|
||||||
|
|
||||||
|
@ -201,8 +200,7 @@ find_removable_dio(uip_ipaddr_t *from, rpl_dio_t *dio)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add the new neighbor only if it is better than the worst parent. */
|
/* Add the new neighbor only if it is better than the worst parent. */
|
||||||
rank = instance->of->calculate_rank(NULL, dio->rank);
|
if(dio->rank + instance->min_hoprankinc < worst_rank - instance->min_hoprankinc / 2) {
|
||||||
if(rank < worst_rank - instance->min_hoprankinc / 2) {
|
|
||||||
/* Found *great* neighbor - add! */
|
/* Found *great* neighbor - add! */
|
||||||
PRINTF("Found better neighbor %d < %d - add to cache...\n",
|
PRINTF("Found better neighbor %d < %d - add to cache...\n",
|
||||||
rank, worst_rank);
|
rank, worst_rank);
|
||||||
|
|
|
@ -27,11 +27,12 @@
|
||||||
* SUCH DAMAGE.
|
* SUCH DAMAGE.
|
||||||
*
|
*
|
||||||
* This file is part of the Contiki operating system.
|
* This file is part of the Contiki operating system.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \file
|
* \file
|
||||||
* An implementation of RPL's objective function 0.
|
* An implementation of RPL's objective function 0, RFC6552
|
||||||
*
|
*
|
||||||
* \author Joakim Eriksson <joakime@sics.se>, Nicolas Tsiftes <nvt@sics.se>
|
* \author Joakim Eriksson <joakime@sics.se>, Nicolas Tsiftes <nvt@sics.se>
|
||||||
*/
|
*/
|
||||||
|
@ -41,136 +42,197 @@
|
||||||
* @{
|
* @{
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "net/rpl/rpl.h"
|
||||||
#include "net/rpl/rpl-private.h"
|
#include "net/rpl/rpl-private.h"
|
||||||
|
#include "net/nbr-table.h"
|
||||||
|
#include "net/link-stats.h"
|
||||||
|
|
||||||
#define DEBUG DEBUG_NONE
|
#define DEBUG DEBUG_NONE
|
||||||
#include "net/ip/uip-debug.h"
|
#include "net/ip/uip-debug.h"
|
||||||
|
|
||||||
static void reset(rpl_dag_t *);
|
/* Constants from RFC6552. We use the default values. */
|
||||||
static rpl_parent_t *best_parent(rpl_parent_t *, rpl_parent_t *);
|
#define RANK_STRETCH 0 /* Must be in the range [0;5] */
|
||||||
static rpl_dag_t *best_dag(rpl_dag_t *, rpl_dag_t *);
|
#define RANK_FACTOR 1 /* Must be in the range [1;4] */
|
||||||
static rpl_rank_t calculate_rank(rpl_parent_t *, rpl_rank_t);
|
|
||||||
static void update_metric_container(rpl_instance_t *);
|
|
||||||
|
|
||||||
rpl_of_t rpl_of0 = {
|
#define MIN_STEP_OF_RANK 1
|
||||||
reset,
|
#define MAX_STEP_OF_RANK 9
|
||||||
NULL,
|
|
||||||
#if RPL_WITH_DAO_ACK
|
|
||||||
NULL,
|
|
||||||
#endif
|
|
||||||
best_parent,
|
|
||||||
best_dag,
|
|
||||||
calculate_rank,
|
|
||||||
update_metric_container,
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
#define DEFAULT_RANK_INCREMENT RPL_MIN_HOPRANKINC
|
/* OF0 computes rank increase as follows:
|
||||||
|
* rank_increase = (RANK_FACTOR * STEP_OF_RANK + RANK_STRETCH) * min_hop_rank_increase
|
||||||
|
* STEP_OF_RANK is an implementation-specific scalar value in the range [1;9].
|
||||||
|
* RFC6552 provides a default value of 3 but recommends to use a dynamic link metric
|
||||||
|
* such as ETX.
|
||||||
|
* */
|
||||||
|
|
||||||
#define MIN_DIFFERENCE (RPL_MIN_HOPRANKINC + RPL_MIN_HOPRANKINC / 2)
|
#define RPL_OF0_FIXED_SR 0
|
||||||
|
#define RPL_OF0_ETX_BASED_SR 1
|
||||||
|
/* Select RPL_OF0_FIXED_SR or RPL_OF0_ETX_BASED_SR */
|
||||||
|
#ifdef RPL_OF0_CONF_SR
|
||||||
|
#define RPL_OF0_SR RPL_OF0_CONF_SR
|
||||||
|
#else /* RPL_OF0_CONF_SR */
|
||||||
|
#define RPL_OF0_SR RPL_OF0_ETX_BASED_SR
|
||||||
|
#endif /* RPL_OF0_CONF_SR */
|
||||||
|
|
||||||
|
#if RPL_OF0_FIXED_SR
|
||||||
|
#define STEP_OF_RANK(p) (3)
|
||||||
|
#endif /* RPL_OF0_FIXED_SR */
|
||||||
|
|
||||||
|
#if RPL_OF0_ETX_BASED_SR
|
||||||
|
/* Numbers suggested by P. Thubert for in the 6TiSCH WG. Anything that maps ETX to
|
||||||
|
* a step between 1 and 9 works. */
|
||||||
|
#define STEP_OF_RANK(p) (((3 * parent_link_metric(p)) / LINK_STATS_ETX_DIVISOR) - 2)
|
||||||
|
#endif /* RPL_OF0_ETX_BASED_SR */
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static void
|
static void
|
||||||
reset(rpl_dag_t *dag)
|
reset(rpl_dag_t *dag)
|
||||||
{
|
{
|
||||||
PRINTF("RPL: Resetting OF0\n");
|
PRINTF("RPL: Reset OF0\n");
|
||||||
}
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static rpl_rank_t
|
#if RPL_WITH_DAO_ACK
|
||||||
calculate_rank(rpl_parent_t *p, rpl_rank_t base_rank)
|
static void
|
||||||
|
dao_ack_callback(rpl_parent_t *p, int status)
|
||||||
{
|
{
|
||||||
rpl_rank_t increment;
|
if(status == RPL_DAO_ACK_UNABLE_TO_ADD_ROUTE_AT_ROOT) {
|
||||||
if(base_rank == 0) {
|
return;
|
||||||
if(p == NULL) {
|
|
||||||
return INFINITE_RANK;
|
|
||||||
}
|
|
||||||
base_rank = p->rank;
|
|
||||||
}
|
}
|
||||||
|
/* here we need to handle failed DAO's and other stuff */
|
||||||
increment = p != NULL ?
|
PRINTF("RPL: OF0 - DAO ACK received with status: %d\n", status);
|
||||||
p->dag->instance->min_hoprankinc :
|
if(status >= RPL_DAO_ACK_UNABLE_TO_ACCEPT) {
|
||||||
DEFAULT_RANK_INCREMENT;
|
/* punish the ETX as if this was 10 packets lost */
|
||||||
|
link_stats_packet_sent(rpl_get_parent_lladdr(p), MAC_TX_OK, 10);
|
||||||
if((rpl_rank_t)(base_rank + increment) < base_rank) {
|
} else if(status == RPL_DAO_ACK_TIMEOUT) { /* timeout = no ack */
|
||||||
PRINTF("RPL: OF0 rank %d incremented to infinite rank due to wrapping\n",
|
/* punish the total lack of ACK with a similar punishment */
|
||||||
base_rank);
|
link_stats_packet_sent(rpl_get_parent_lladdr(p), MAC_TX_OK, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* RPL_WITH_DAO_ACK */
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static uint16_t
|
||||||
|
parent_link_metric(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
/* OF0 operates without metric container; the only metric we have is ETX */
|
||||||
|
const struct link_stats *stats = rpl_get_parent_link_stats(p);
|
||||||
|
return stats != NULL ? stats->etx : 0xffff;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static uint16_t
|
||||||
|
parent_rank_increase(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
uint16_t min_hoprankinc;
|
||||||
|
if(p == NULL || p->dag == NULL || p->dag->instance == NULL) {
|
||||||
return INFINITE_RANK;
|
return INFINITE_RANK;
|
||||||
}
|
}
|
||||||
return base_rank + increment;
|
min_hoprankinc = p->dag->instance->min_hoprankinc;
|
||||||
|
return (RANK_FACTOR * STEP_OF_RANK(p) + RANK_STRETCH) * min_hoprankinc;
|
||||||
}
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static rpl_dag_t *
|
static uint16_t
|
||||||
best_dag(rpl_dag_t *d1, rpl_dag_t *d2)
|
parent_path_cost(rpl_parent_t *p)
|
||||||
{
|
{
|
||||||
if(d1->grounded) {
|
if(p == NULL) {
|
||||||
if (!d2->grounded) {
|
return 0xffff;
|
||||||
return d1;
|
|
||||||
}
|
|
||||||
} else if(d2->grounded) {
|
|
||||||
return d2;
|
|
||||||
}
|
}
|
||||||
|
/* path cost upper bound: 0xffff */
|
||||||
if(d1->preference < d2->preference) {
|
return MIN((uint32_t)p->rank + parent_link_metric(p), 0xffff);
|
||||||
return d2;
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static rpl_rank_t
|
||||||
|
rank_via_parent(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
if(p == NULL) {
|
||||||
|
return INFINITE_RANK;
|
||||||
} else {
|
} else {
|
||||||
if(d1->preference > d2->preference) {
|
return MIN((uint32_t)p->rank + parent_rank_increase(p), INFINITE_RANK);
|
||||||
return d1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(d2->rank < d1->rank) {
|
|
||||||
return d2;
|
|
||||||
} else {
|
|
||||||
return d1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static int
|
||||||
|
parent_is_acceptable(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
return STEP_OF_RANK(p) >= MIN_STEP_OF_RANK
|
||||||
|
&& STEP_OF_RANK(p) <= MAX_STEP_OF_RANK;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static int
|
||||||
|
parent_has_usable_link(rpl_parent_t *p)
|
||||||
|
{
|
||||||
|
return parent_is_acceptable(p);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static rpl_parent_t *
|
static rpl_parent_t *
|
||||||
best_parent(rpl_parent_t *p1, rpl_parent_t *p2)
|
best_parent(rpl_parent_t *p1, rpl_parent_t *p2)
|
||||||
{
|
{
|
||||||
rpl_rank_t r1, r2;
|
|
||||||
rpl_dag_t *dag;
|
rpl_dag_t *dag;
|
||||||
uip_ds6_nbr_t *nbr1, *nbr2;
|
uint16_t p1_cost;
|
||||||
nbr1 = rpl_get_nbr(p1);
|
uint16_t p2_cost;
|
||||||
nbr2 = rpl_get_nbr(p2);
|
int p1_is_acceptable;
|
||||||
|
int p2_is_acceptable;
|
||||||
|
|
||||||
dag = (rpl_dag_t *)p1->dag; /* Both parents must be in the same DAG. */
|
p1_is_acceptable = p1 != NULL && parent_is_acceptable(p1);
|
||||||
|
p2_is_acceptable = p2 != NULL && parent_is_acceptable(p2);
|
||||||
|
|
||||||
if(nbr1 == NULL || nbr2 == NULL) {
|
if(!p1_is_acceptable) {
|
||||||
return dag->preferred_parent;
|
return p2_is_acceptable ? p2 : NULL;
|
||||||
|
}
|
||||||
|
if(!p2_is_acceptable) {
|
||||||
|
return p1_is_acceptable ? p1 : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
PRINTF("RPL: Comparing parent ");
|
dag = p1->dag; /* Both parents are in the same DAG. */
|
||||||
PRINT6ADDR(rpl_get_parent_ipaddr(p1));
|
p1_cost = parent_path_cost(p1);
|
||||||
PRINTF(" (confidence %d, rank %d) with parent ",
|
p2_cost = parent_path_cost(p2);
|
||||||
nbr1->link_metric, p1->rank);
|
|
||||||
PRINT6ADDR(rpl_get_parent_ipaddr(p2));
|
|
||||||
PRINTF(" (confidence %d, rank %d)\n",
|
|
||||||
nbr2->link_metric, p2->rank);
|
|
||||||
|
|
||||||
|
/* Paths costs coarse-grained (multiple of min_hoprankinc), we operate without hysteresis */
|
||||||
r1 = DAG_RANK(p1->rank, p1->dag->instance) * RPL_MIN_HOPRANKINC +
|
if(p1_cost != p2_cost) {
|
||||||
nbr1->link_metric;
|
/* Pick parent with lowest path cost */
|
||||||
r2 = DAG_RANK(p2->rank, p1->dag->instance) * RPL_MIN_HOPRANKINC +
|
return p1_cost < p2_cost ? p1 : p2;
|
||||||
nbr2->link_metric;
|
|
||||||
/* Compare two parents by looking both and their rank and at the ETX
|
|
||||||
for that parent. We choose the parent that has the most
|
|
||||||
favourable combination. */
|
|
||||||
|
|
||||||
if(r1 < r2 + MIN_DIFFERENCE &&
|
|
||||||
r1 > r2 - MIN_DIFFERENCE) {
|
|
||||||
return dag->preferred_parent;
|
|
||||||
} else if(r1 < r2) {
|
|
||||||
return p1;
|
|
||||||
} else {
|
} else {
|
||||||
return p2;
|
/* We have a tie! */
|
||||||
|
/* Stik to current preferred parent if possible */
|
||||||
|
if(p1 == dag->preferred_parent || p2 == dag->preferred_parent) {
|
||||||
|
return dag->preferred_parent;
|
||||||
|
}
|
||||||
|
/* None of the nodes is the current preferred parent,
|
||||||
|
* choose parent with best link metric */
|
||||||
|
return parent_link_metric(p1) < parent_link_metric(p2) ? p1 : p2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static rpl_dag_t *
|
||||||
|
best_dag(rpl_dag_t *d1, rpl_dag_t *d2)
|
||||||
|
{
|
||||||
|
if(d1->grounded != d2->grounded) {
|
||||||
|
return d1->grounded ? d1 : d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(d1->preference != d2->preference) {
|
||||||
|
return d1->preference > d2->preference ? d1 : d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d1->rank < d2->rank ? d1 : d2;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
static void
|
static void
|
||||||
update_metric_container(rpl_instance_t *instance)
|
update_metric_container(rpl_instance_t *instance)
|
||||||
{
|
{
|
||||||
instance->mc.type = RPL_DAG_MC_NONE;
|
instance->mc.type = RPL_DAG_MC_NONE;
|
||||||
}
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
rpl_of_t rpl_of0 = {
|
||||||
|
reset,
|
||||||
|
#if RPL_WITH_DAO_ACK
|
||||||
|
dao_ack_callback,
|
||||||
|
#endif
|
||||||
|
parent_link_metric,
|
||||||
|
parent_has_usable_link,
|
||||||
|
parent_path_cost,
|
||||||
|
rank_via_parent,
|
||||||
|
best_parent,
|
||||||
|
best_dag,
|
||||||
|
update_metric_container,
|
||||||
|
RPL_OCP_OF0
|
||||||
|
};
|
||||||
|
|
||||||
/** @}*/
|
/** @}*/
|
||||||
|
|
|
@ -144,11 +144,28 @@
|
||||||
((unsigned long)(instance)->lifetime_unit * (lifetime))
|
((unsigned long)(instance)->lifetime_unit * (lifetime))
|
||||||
|
|
||||||
#ifndef RPL_CONF_MIN_HOPRANKINC
|
#ifndef RPL_CONF_MIN_HOPRANKINC
|
||||||
|
/* RFC6550 defines the default MIN_HOPRANKINC as 256.
|
||||||
|
* However, we use MRHOF as a default Objective Function (RFC6719),
|
||||||
|
* which recommends setting MIN_HOPRANKINC with care, in particular
|
||||||
|
* when used with ETX as a metric. ETX is computed as a fixed point
|
||||||
|
* real with a divisor of 128 (RFC6719, RFC6551). We choose to also
|
||||||
|
* use 128 for RPL_MIN_HOPRANKINC, resulting in a rank equal to the
|
||||||
|
* ETX path cost. Larger values may also be desirable, as discussed
|
||||||
|
* in section 6.1 of RFC6719. */
|
||||||
|
#if RPL_OF_OCP == RPL_OCP_MRHOF
|
||||||
|
#define RPL_MIN_HOPRANKINC 128
|
||||||
|
#else /* RPL_OF_OCP == RPL_OCP_MRHOF */
|
||||||
#define RPL_MIN_HOPRANKINC 256
|
#define RPL_MIN_HOPRANKINC 256
|
||||||
#else
|
#endif /* RPL_OF_OCP == RPL_OCP_MRHOF */
|
||||||
|
#else /* RPL_CONF_MIN_HOPRANKINC */
|
||||||
#define RPL_MIN_HOPRANKINC RPL_CONF_MIN_HOPRANKINC
|
#define RPL_MIN_HOPRANKINC RPL_CONF_MIN_HOPRANKINC
|
||||||
#endif
|
#endif /* RPL_CONF_MIN_HOPRANKINC */
|
||||||
|
|
||||||
|
#ifndef RPL_CONF_MAX_RANKINC
|
||||||
#define RPL_MAX_RANKINC (7 * RPL_MIN_HOPRANKINC)
|
#define RPL_MAX_RANKINC (7 * RPL_MIN_HOPRANKINC)
|
||||||
|
#else /* RPL_CONF_MAX_RANKINC */
|
||||||
|
#define RPL_MAX_RANKINC RPL_CONF_MAX_RANKINC
|
||||||
|
#endif /* RPL_CONF_MAX_RANKINC */
|
||||||
|
|
||||||
#define DAG_RANK(fixpt_rank, instance) \
|
#define DAG_RANK(fixpt_rank, instance) \
|
||||||
((fixpt_rank) / (instance)->min_hoprankinc)
|
((fixpt_rank) / (instance)->min_hoprankinc)
|
||||||
|
@ -199,13 +216,6 @@
|
||||||
#define RPL_MCAST_LIFETIME 3
|
#define RPL_MCAST_LIFETIME 3
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
|
||||||
* The ETX in the metric container is expressed as a fixed-point value
|
|
||||||
* whose integer part can be obtained by dividing the value by
|
|
||||||
* RPL_DAG_MC_ETX_DIVISOR.
|
|
||||||
*/
|
|
||||||
#define RPL_DAG_MC_ETX_DIVISOR 256
|
|
||||||
|
|
||||||
/* DIS related */
|
/* DIS related */
|
||||||
#define RPL_DIS_SEND 1
|
#define RPL_DIS_SEND 1
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
|
|
||||||
#include "contiki-conf.h"
|
#include "contiki-conf.h"
|
||||||
#include "net/rpl/rpl-private.h"
|
#include "net/rpl/rpl-private.h"
|
||||||
|
#include "net/link-stats.h"
|
||||||
#include "net/ipv6/multicast/uip-mcast6.h"
|
#include "net/ipv6/multicast/uip-mcast6.h"
|
||||||
#include "lib/random.h"
|
#include "lib/random.h"
|
||||||
#include "sys/ctimer.h"
|
#include "sys/ctimer.h"
|
||||||
|
@ -55,6 +56,14 @@
|
||||||
void RPL_CALLBACK_NEW_DIO_INTERVAL(uint8_t dio_interval);
|
void RPL_CALLBACK_NEW_DIO_INTERVAL(uint8_t dio_interval);
|
||||||
#endif /* RPL_CALLBACK_NEW_DIO_INTERVAL */
|
#endif /* RPL_CALLBACK_NEW_DIO_INTERVAL */
|
||||||
|
|
||||||
|
#ifdef RPL_PROBING_SELECT_FUNC
|
||||||
|
rpl_parent_t *RPL_PROBING_SELECT_FUNC(rpl_dag_t *dag);
|
||||||
|
#endif /* RPL_PROBING_SELECT_FUNC */
|
||||||
|
|
||||||
|
#ifdef RPL_PROBING_DELAY_FUNC
|
||||||
|
clock_time_t RPL_PROBING_DELAY_FUNC(rpl_dag_t *dag);
|
||||||
|
#endif /* RPL_PROBING_DELAY_FUNC */
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
static struct ctimer periodic_timer;
|
static struct ctimer periodic_timer;
|
||||||
|
|
||||||
|
@ -358,42 +367,57 @@ rpl_schedule_unicast_dio_immediately(rpl_instance_t *instance)
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
#if RPL_WITH_PROBING
|
#if RPL_WITH_PROBING
|
||||||
static rpl_parent_t *
|
clock_time_t
|
||||||
|
get_probing_delay(rpl_dag_t *dag)
|
||||||
|
{
|
||||||
|
if(dag != NULL && dag->instance != NULL
|
||||||
|
&& dag->instance->urgent_probing_target != NULL) {
|
||||||
|
/* Urgent probing needed (to find out if a neighbor may become preferred parent) */
|
||||||
|
return random_rand() % (CLOCK_SECOND * 10);
|
||||||
|
} else {
|
||||||
|
/* Else, use normal probing interval */
|
||||||
|
return ((RPL_PROBING_INTERVAL) / 2) + random_rand() % (RPL_PROBING_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
rpl_parent_t *
|
||||||
get_probing_target(rpl_dag_t *dag)
|
get_probing_target(rpl_dag_t *dag)
|
||||||
{
|
{
|
||||||
/* Returns the next probing target. The current implementation probes the current
|
/* Returns the next probing target. The current implementation probes the urgent
|
||||||
* preferred parent if we have not updated its link for RPL_PROBING_EXPIRATION_TIME.
|
* probing target if any, or the preferred parent if its link statistics need refresh.
|
||||||
* Otherwise, it picks at random between:
|
* Otherwise, it picks at random between:
|
||||||
* (1) selecting the best parent not updated for RPL_PROBING_EXPIRATION_TIME
|
* (1) selecting the best parent with non-fresh link statistics
|
||||||
* (2) selecting the least recently updated parent
|
* (2) selecting the least recently updated parent
|
||||||
*/
|
*/
|
||||||
|
|
||||||
rpl_parent_t *p;
|
rpl_parent_t *p;
|
||||||
rpl_parent_t *probing_target = NULL;
|
rpl_parent_t *probing_target = NULL;
|
||||||
rpl_rank_t probing_target_rank = INFINITE_RANK;
|
rpl_rank_t probing_target_rank = INFINITE_RANK;
|
||||||
/* min_last_tx is the clock time RPL_PROBING_EXPIRATION_TIME in the past */
|
clock_time_t probing_target_age = 0;
|
||||||
clock_time_t min_last_tx = clock_time();
|
clock_time_t clock_now = clock_time();
|
||||||
min_last_tx = min_last_tx > 2 * RPL_PROBING_EXPIRATION_TIME
|
|
||||||
? min_last_tx - RPL_PROBING_EXPIRATION_TIME : 1;
|
|
||||||
|
|
||||||
if(dag == NULL ||
|
if(dag == NULL ||
|
||||||
dag->instance == NULL ||
|
dag->instance == NULL) {
|
||||||
dag->preferred_parent == NULL) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Our preferred parent needs probing */
|
/* There is an urgent probing target */
|
||||||
if(dag->preferred_parent->last_tx_time < min_last_tx) {
|
if(dag->instance->urgent_probing_target != NULL) {
|
||||||
probing_target = dag->preferred_parent;
|
return dag->instance->urgent_probing_target;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* With 50% probability: probe best parent not updated for RPL_PROBING_EXPIRATION_TIME */
|
/* The preferred parent needs probing */
|
||||||
if(probing_target == NULL && (random_rand() % 2) == 0) {
|
if(dag->preferred_parent != NULL && !rpl_parent_is_fresh(dag->preferred_parent)) {
|
||||||
|
return dag->preferred_parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* With 50% probability: probe best non-fresh parent */
|
||||||
|
if(random_rand() % 2 == 0) {
|
||||||
p = nbr_table_head(rpl_parents);
|
p = nbr_table_head(rpl_parents);
|
||||||
while(p != NULL) {
|
while(p != NULL) {
|
||||||
if(p->dag == dag && p->last_tx_time < min_last_tx) {
|
if(p->dag == dag && !rpl_parent_is_fresh(p)) {
|
||||||
/* p is in our dag and needs probing */
|
/* p is in our dag and needs probing */
|
||||||
rpl_rank_t p_rank = dag->instance->of->calculate_rank(p, 0);
|
rpl_rank_t p_rank = rpl_rank_via_parent(p);
|
||||||
if(probing_target == NULL
|
if(probing_target == NULL
|
||||||
|| p_rank < probing_target_rank) {
|
|| p_rank < probing_target_rank) {
|
||||||
probing_target = p;
|
probing_target = p;
|
||||||
|
@ -404,14 +428,16 @@ get_probing_target(rpl_dag_t *dag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The default probing target is the least recently updated parent */
|
/* If we still do not have a probing target: pick the least recently updated parent */
|
||||||
if(probing_target == NULL) {
|
if(probing_target == NULL) {
|
||||||
p = nbr_table_head(rpl_parents);
|
p = nbr_table_head(rpl_parents);
|
||||||
while(p != NULL) {
|
while(p != NULL) {
|
||||||
if(p->dag == dag) {
|
const struct link_stats *stats =rpl_get_parent_link_stats(p);
|
||||||
|
if(p->dag == dag && stats != NULL) {
|
||||||
if(probing_target == NULL
|
if(probing_target == NULL
|
||||||
|| p->last_tx_time < probing_target->last_tx_time) {
|
|| clock_now - stats->last_tx_time > probing_target_age) {
|
||||||
probing_target = p;
|
probing_target = p;
|
||||||
|
probing_target_age = clock_now - stats->last_tx_time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p = nbr_table_next(rpl_parents, p);
|
p = nbr_table_next(rpl_parents, p);
|
||||||
|
@ -430,11 +456,17 @@ handle_probing_timer(void *ptr)
|
||||||
|
|
||||||
/* Perform probing */
|
/* Perform probing */
|
||||||
if(target_ipaddr != NULL) {
|
if(target_ipaddr != NULL) {
|
||||||
PRINTF("RPL: probing %u ((last tx %u min ago))\n",
|
const struct link_stats *stats = rpl_get_parent_link_stats(probing_target);
|
||||||
nbr_table_get_lladdr(rpl_parents, probing_target)->u8[7],
|
(void)stats;
|
||||||
(unsigned)((clock_time() - probing_target->last_tx_time) / (60 * CLOCK_SECOND)));
|
PRINTF("RPL: probing %u %s last tx %u min ago\n",
|
||||||
/* Send probe (unicast DIO or DIS) */
|
rpl_get_parent_llpaddr(probing_target)->u8[7],
|
||||||
|
instance->urgent_probing_target != NULL ? "(urgent)" : "",
|
||||||
|
probing_target != NULL ?
|
||||||
|
(unsigned)((clock_time() - stats->last_tx_time) / (60 * CLOCK_SECOND)) : 0
|
||||||
|
);
|
||||||
|
/* Send probe, e.g. unicast DIO or DIS */
|
||||||
RPL_PROBING_SEND_FUNC(instance, target_ipaddr);
|
RPL_PROBING_SEND_FUNC(instance, target_ipaddr);
|
||||||
|
instance->urgent_probing_target = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Schedule next probing */
|
/* Schedule next probing */
|
||||||
|
@ -448,7 +480,7 @@ handle_probing_timer(void *ptr)
|
||||||
void
|
void
|
||||||
rpl_schedule_probing(rpl_instance_t *instance)
|
rpl_schedule_probing(rpl_instance_t *instance)
|
||||||
{
|
{
|
||||||
ctimer_set(&instance->probing_timer, RPL_PROBING_DELAY_FUNC(),
|
ctimer_set(&instance->probing_timer, RPL_PROBING_DELAY_FUNC(instance->current_dag),
|
||||||
handle_probing_timer, instance);
|
handle_probing_timer, instance);
|
||||||
}
|
}
|
||||||
#endif /* RPL_WITH_PROBING */
|
#endif /* RPL_WITH_PROBING */
|
||||||
|
|
|
@ -215,12 +215,10 @@ rpl_remove_routes_by_nexthop(uip_ipaddr_t *nexthop, rpl_dag_t *dag)
|
||||||
|
|
||||||
while(r != NULL) {
|
while(r != NULL) {
|
||||||
if(uip_ipaddr_cmp(uip_ds6_route_nexthop(r), nexthop) &&
|
if(uip_ipaddr_cmp(uip_ds6_route_nexthop(r), nexthop) &&
|
||||||
r->state.dag == dag) {
|
r->state.dag == dag) {
|
||||||
uip_ds6_route_rm(r);
|
r->state.lifetime = 0;
|
||||||
r = uip_ds6_route_head();
|
|
||||||
} else {
|
|
||||||
r = uip_ds6_route_next(r);
|
|
||||||
}
|
}
|
||||||
|
r = uip_ds6_route_next(r);
|
||||||
}
|
}
|
||||||
ANNOTATE("#L %u 0\n", nexthop->u8[sizeof(uip_ipaddr_t) - 1]);
|
ANNOTATE("#L %u 0\n", nexthop->u8[sizeof(uip_ipaddr_t) - 1]);
|
||||||
}
|
}
|
||||||
|
@ -268,10 +266,6 @@ rpl_link_neighbor_callback(const linkaddr_t *addr, int status, int numtx)
|
||||||
/* Trigger DAG rank recalculation. */
|
/* Trigger DAG rank recalculation. */
|
||||||
PRINTF("RPL: rpl_link_neighbor_callback triggering update\n");
|
PRINTF("RPL: rpl_link_neighbor_callback triggering update\n");
|
||||||
parent->flags |= RPL_PARENT_FLAG_UPDATED;
|
parent->flags |= RPL_PARENT_FLAG_UPDATED;
|
||||||
if(instance->of->neighbor_link_callback != NULL) {
|
|
||||||
instance->of->neighbor_link_callback(parent, status, numtx);
|
|
||||||
parent->last_tx_time = clock_time();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -350,8 +344,6 @@ rpl_init(void)
|
||||||
#if RPL_CONF_STATS
|
#if RPL_CONF_STATS
|
||||||
memset(&rpl_stats, 0, sizeof(rpl_stats));
|
memset(&rpl_stats, 0, sizeof(rpl_stats));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
RPL_OF.reset(NULL);
|
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,8 @@
|
||||||
typedef uint16_t rpl_rank_t;
|
typedef uint16_t rpl_rank_t;
|
||||||
typedef uint16_t rpl_ocp_t;
|
typedef uint16_t rpl_ocp_t;
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
/* DAG Metric Container Object Types, to be confirmed by IANA. */
|
/* IANA Routing Metric/Constraint Type as defined in RFC6551 */
|
||||||
#define RPL_DAG_MC_NONE 0 /* Local identifier for empty MC */
|
#define RPL_DAG_MC_NONE 0 /* Local identifier for empty MC */
|
||||||
#define RPL_DAG_MC_NSA 1 /* Node State and Attributes */
|
#define RPL_DAG_MC_NSA 1 /* Node State and Attributes */
|
||||||
#define RPL_DAG_MC_ENERGY 2 /* Node Energy */
|
#define RPL_DAG_MC_ENERGY 2 /* Node Energy */
|
||||||
#define RPL_DAG_MC_HOPCOUNT 3 /* Hop Count */
|
#define RPL_DAG_MC_HOPCOUNT 3 /* Hop Count */
|
||||||
|
@ -60,27 +60,31 @@ typedef uint16_t rpl_ocp_t;
|
||||||
#define RPL_DAG_MC_ETX 7 /* Expected Transmission Count */
|
#define RPL_DAG_MC_ETX 7 /* Expected Transmission Count */
|
||||||
#define RPL_DAG_MC_LC 8 /* Link Color */
|
#define RPL_DAG_MC_LC 8 /* Link Color */
|
||||||
|
|
||||||
/* DAG Metric Container flags. */
|
/* IANA Routing Metric/Constraint Common Header Flag field as defined in RFC6551 (bit indexes) */
|
||||||
#define RPL_DAG_MC_FLAG_P 0x8
|
#define RPL_DAG_MC_FLAG_P 5
|
||||||
#define RPL_DAG_MC_FLAG_C 0x4
|
#define RPL_DAG_MC_FLAG_C 6
|
||||||
#define RPL_DAG_MC_FLAG_O 0x2
|
#define RPL_DAG_MC_FLAG_O 7
|
||||||
#define RPL_DAG_MC_FLAG_R 0x1
|
#define RPL_DAG_MC_FLAG_R 8
|
||||||
|
|
||||||
/* DAG Metric Container aggregation mode. */
|
/* IANA Routing Metric/Constraint Common Header A Field as defined in RFC6551 */
|
||||||
#define RPL_DAG_MC_AGGR_ADDITIVE 0
|
#define RPL_DAG_MC_AGGR_ADDITIVE 0
|
||||||
#define RPL_DAG_MC_AGGR_MAXIMUM 1
|
#define RPL_DAG_MC_AGGR_MAXIMUM 1
|
||||||
#define RPL_DAG_MC_AGGR_MINIMUM 2
|
#define RPL_DAG_MC_AGGR_MINIMUM 2
|
||||||
#define RPL_DAG_MC_AGGR_MULTIPLICATIVE 3
|
#define RPL_DAG_MC_AGGR_MULTIPLICATIVE 3
|
||||||
|
|
||||||
/* The bit index within the flags field of
|
/* The bit index within the flags field of the rpl_metric_object_energy structure. */
|
||||||
the rpl_metric_object_energy structure. */
|
#define RPL_DAG_MC_ENERGY_INCLUDED 3
|
||||||
#define RPL_DAG_MC_ENERGY_INCLUDED 3
|
#define RPL_DAG_MC_ENERGY_TYPE 1
|
||||||
#define RPL_DAG_MC_ENERGY_TYPE 1
|
#define RPL_DAG_MC_ENERGY_ESTIMATION 0
|
||||||
#define RPL_DAG_MC_ENERGY_ESTIMATION 0
|
|
||||||
|
|
||||||
#define RPL_DAG_MC_ENERGY_TYPE_MAINS 0
|
/* IANA Node Type Field as defined in RFC6551 */
|
||||||
#define RPL_DAG_MC_ENERGY_TYPE_BATTERY 1
|
#define RPL_DAG_MC_ENERGY_TYPE_MAINS 0
|
||||||
#define RPL_DAG_MC_ENERGY_TYPE_SCAVENGING 2
|
#define RPL_DAG_MC_ENERGY_TYPE_BATTERY 1
|
||||||
|
#define RPL_DAG_MC_ENERGY_TYPE_SCAVENGING 2
|
||||||
|
|
||||||
|
/* IANA Objective Code Point as defined in RFC6550 */
|
||||||
|
#define RPL_OCP_OF0 0
|
||||||
|
#define RPL_OCP_MRHOF 1
|
||||||
|
|
||||||
struct rpl_metric_object_energy {
|
struct rpl_metric_object_energy {
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
|
@ -109,11 +113,10 @@ struct rpl_dag;
|
||||||
|
|
||||||
struct rpl_parent {
|
struct rpl_parent {
|
||||||
struct rpl_dag *dag;
|
struct rpl_dag *dag;
|
||||||
#if RPL_DAG_MC != RPL_DAG_MC_NONE
|
#if RPL_WITH_MC
|
||||||
rpl_metric_container_t mc;
|
rpl_metric_container_t mc;
|
||||||
#endif /* RPL_DAG_MC != RPL_DAG_MC_NONE */
|
#endif /* RPL_WITH_MC */
|
||||||
rpl_rank_t rank;
|
rpl_rank_t rank;
|
||||||
clock_time_t last_tx_time;
|
|
||||||
uint8_t dtsn;
|
uint8_t dtsn;
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
};
|
};
|
||||||
|
@ -155,11 +158,25 @@ typedef struct rpl_instance rpl_instance_t;
|
||||||
* Resets the objective function state for a specific DAG. This function is
|
* Resets the objective function state for a specific DAG. This function is
|
||||||
* called when doing a global repair on the DAG.
|
* called when doing a global repair on the DAG.
|
||||||
*
|
*
|
||||||
* neighbor_link_callback(parent, known, etx)
|
* parent_link_metric(parent)
|
||||||
*
|
*
|
||||||
* Receives link-layer neighbor information. The parameter "known" is set
|
* Returns the link metric of a parent
|
||||||
* either to 0 or 1. The "etx" parameter specifies the current
|
*
|
||||||
* ETX(estimated transmissions) for the neighbor.
|
* parent_has_usable_link(parent)
|
||||||
|
*
|
||||||
|
* Returns 1 iff we have a usable link to this parent
|
||||||
|
*
|
||||||
|
* parent_path_cost(parent)
|
||||||
|
*
|
||||||
|
* Returns the path cost of a parent
|
||||||
|
*
|
||||||
|
* rank_via_parent(parent)
|
||||||
|
*
|
||||||
|
* Returns our rank if we select a given parent as preferred parent
|
||||||
|
*
|
||||||
|
* parent_is_acceptable
|
||||||
|
*
|
||||||
|
* Returns 1 if a parent is usable as preferred parent, 0 otherwise
|
||||||
*
|
*
|
||||||
* best_parent(parent1, parent2)
|
* best_parent(parent1, parent2)
|
||||||
*
|
*
|
||||||
|
@ -169,13 +186,6 @@ typedef struct rpl_instance rpl_instance_t;
|
||||||
*
|
*
|
||||||
* Compares two DAGs and returns the best one, according to the OF.
|
* Compares two DAGs and returns the best one, according to the OF.
|
||||||
*
|
*
|
||||||
* calculate_rank(parent, base_rank)
|
|
||||||
*
|
|
||||||
* Calculates a rank value using the parent rank and a base rank.
|
|
||||||
* If "parent" is NULL, the objective function selects a default increment
|
|
||||||
* that is adds to the "base_rank". Otherwise, the OF uses information known
|
|
||||||
* about "parent" to select an increment to the "base_rank".
|
|
||||||
*
|
|
||||||
* update_metric_container(dag)
|
* update_metric_container(dag)
|
||||||
*
|
*
|
||||||
* Updates the metric container for outgoing DIOs in a certain DAG.
|
* Updates the metric container for outgoing DIOs in a certain DAG.
|
||||||
|
@ -190,20 +200,20 @@ typedef struct rpl_instance rpl_instance_t;
|
||||||
*/
|
*/
|
||||||
struct rpl_of {
|
struct rpl_of {
|
||||||
void (*reset)(struct rpl_dag *);
|
void (*reset)(struct rpl_dag *);
|
||||||
void (*neighbor_link_callback)(rpl_parent_t *, int, int);
|
|
||||||
#if RPL_WITH_DAO_ACK
|
#if RPL_WITH_DAO_ACK
|
||||||
void (*dao_ack_callback)(rpl_parent_t *, int status);
|
void (*dao_ack_callback)(rpl_parent_t *, int status);
|
||||||
#endif
|
#endif
|
||||||
|
uint16_t (*parent_link_metric)(rpl_parent_t *);
|
||||||
|
int (*parent_has_usable_link)(rpl_parent_t *);
|
||||||
|
uint16_t (*parent_path_cost)(rpl_parent_t *);
|
||||||
|
rpl_rank_t (*rank_via_parent)(rpl_parent_t *);
|
||||||
rpl_parent_t *(*best_parent)(rpl_parent_t *, rpl_parent_t *);
|
rpl_parent_t *(*best_parent)(rpl_parent_t *, rpl_parent_t *);
|
||||||
rpl_dag_t *(*best_dag)(rpl_dag_t *, rpl_dag_t *);
|
rpl_dag_t *(*best_dag)(rpl_dag_t *, rpl_dag_t *);
|
||||||
rpl_rank_t (*calculate_rank)(rpl_parent_t *, rpl_rank_t);
|
|
||||||
void (*update_metric_container)( rpl_instance_t *);
|
void (*update_metric_container)( rpl_instance_t *);
|
||||||
rpl_ocp_t ocp;
|
rpl_ocp_t ocp;
|
||||||
};
|
};
|
||||||
typedef struct rpl_of rpl_of_t;
|
typedef struct rpl_of rpl_of_t;
|
||||||
|
|
||||||
/* Declare the selected objective function. */
|
|
||||||
extern rpl_of_t RPL_OF;
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
/* Instance */
|
/* Instance */
|
||||||
struct rpl_instance {
|
struct rpl_instance {
|
||||||
|
@ -241,6 +251,7 @@ struct rpl_instance {
|
||||||
clock_time_t dio_next_delay; /* delay for completion of dio interval */
|
clock_time_t dio_next_delay; /* delay for completion of dio interval */
|
||||||
#if RPL_WITH_PROBING
|
#if RPL_WITH_PROBING
|
||||||
struct ctimer probing_timer;
|
struct ctimer probing_timer;
|
||||||
|
rpl_parent_t *urgent_probing_target;
|
||||||
#endif /* RPL_WITH_PROBING */
|
#endif /* RPL_WITH_PROBING */
|
||||||
struct ctimer dio_timer;
|
struct ctimer dio_timer;
|
||||||
struct ctimer dao_timer;
|
struct ctimer dao_timer;
|
||||||
|
@ -268,10 +279,15 @@ int rpl_verify_header(int);
|
||||||
void rpl_insert_header(void);
|
void rpl_insert_header(void);
|
||||||
void rpl_remove_header(void);
|
void rpl_remove_header(void);
|
||||||
uint8_t rpl_invert_header(void);
|
uint8_t rpl_invert_header(void);
|
||||||
|
const struct link_stats *rpl_get_parent_link_stats(rpl_parent_t *p);
|
||||||
|
int rpl_parent_is_fresh(rpl_parent_t *p);
|
||||||
|
int rpl_parent_is_reachable(rpl_parent_t *p);
|
||||||
|
uint16_t rpl_get_parent_link_metric(rpl_parent_t *p);
|
||||||
|
rpl_rank_t rpl_rank_via_parent(rpl_parent_t *p);
|
||||||
|
const linkaddr_t *rpl_get_parent_lladdr(rpl_parent_t *p);
|
||||||
uip_ipaddr_t *rpl_get_parent_ipaddr(rpl_parent_t *nbr);
|
uip_ipaddr_t *rpl_get_parent_ipaddr(rpl_parent_t *nbr);
|
||||||
rpl_parent_t *rpl_get_parent(uip_lladdr_t *addr);
|
rpl_parent_t *rpl_get_parent(uip_lladdr_t *addr);
|
||||||
rpl_rank_t rpl_get_parent_rank(uip_lladdr_t *addr);
|
rpl_rank_t rpl_get_parent_rank(uip_lladdr_t *addr);
|
||||||
uint16_t rpl_get_parent_link_metric(const uip_lladdr_t *addr);
|
|
||||||
void rpl_dag_init(void);
|
void rpl_dag_init(void);
|
||||||
uip_ds6_nbr_t *rpl_get_nbr(rpl_parent_t *parent);
|
uip_ds6_nbr_t *rpl_get_nbr(rpl_parent_t *parent);
|
||||||
void rpl_print_neighbor_list(void);
|
void rpl_print_neighbor_list(void);
|
||||||
|
|
|
@ -56,4 +56,11 @@
|
||||||
|
|
||||||
#define RPL_CONF_DEFAULT_ROUTE_INFINITE_LIFETIME 1
|
#define RPL_CONF_DEFAULT_ROUTE_INFINITE_LIFETIME 1
|
||||||
|
|
||||||
|
/* Save some ROM */
|
||||||
|
#undef UIP_CONF_TCP
|
||||||
|
#define UIP_CONF_TCP 0
|
||||||
|
|
||||||
|
#undef SICSLOWPAN_CONF_FRAG
|
||||||
|
#define SICSLOWPAN_CONF_FRAG 0
|
||||||
|
|
||||||
#endif /* PROJECT_CONF_H_ */
|
#endif /* PROJECT_CONF_H_ */
|
||||||
|
|
|
@ -139,7 +139,7 @@
|
||||||
#undef UIP_CONF_TCP
|
#undef UIP_CONF_TCP
|
||||||
#define UIP_CONF_TCP 0
|
#define UIP_CONF_TCP 0
|
||||||
#undef QUEUEBUF_CONF_NUM
|
#undef QUEUEBUF_CONF_NUM
|
||||||
#define QUEUEBUF_CONF_NUM 4
|
#define QUEUEBUF_CONF_NUM 3
|
||||||
#undef UIP_CONF_MAX_ROUTES
|
#undef UIP_CONF_MAX_ROUTES
|
||||||
#define UIP_CONF_MAX_ROUTES 8
|
#define UIP_CONF_MAX_ROUTES 8
|
||||||
#undef NBR_TABLE_CONF_MAX_NEIGHBORS
|
#undef NBR_TABLE_CONF_MAX_NEIGHBORS
|
||||||
|
|
|
@ -71,5 +71,5 @@ MODULES+=core/net/mac core/net/mac/sicslowmac \
|
||||||
core/net/llsec
|
core/net/llsec
|
||||||
else
|
else
|
||||||
vpath %.c $(CONTIKI)/core/net/ipv6
|
vpath %.c $(CONTIKI)/core/net/ipv6
|
||||||
CONTIKI_SOURCEFILES += sicslowpan.c linkaddr.c
|
CONTIKI_SOURCEFILES += sicslowpan.c linkaddr.c link-stats.c nbr-table.c
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -125,8 +125,8 @@
|
||||||
#define UIP_CONF_ROUTER 0
|
#define UIP_CONF_ROUTER 0
|
||||||
|
|
||||||
/* configure number of neighbors and routes */
|
/* configure number of neighbors and routes */
|
||||||
#define NBR_TABLE_CONF_MAX_NEIGHBORS 5
|
#define NBR_TABLE_CONF_MAX_NEIGHBORS 4
|
||||||
#define UIP_CONF_MAX_ROUTES 5
|
#define UIP_CONF_MAX_ROUTES 4
|
||||||
|
|
||||||
#define RPL_CONF_MAX_PARENTS 4
|
#define RPL_CONF_MAX_PARENTS 4
|
||||||
#define RPL_CONF_MAX_DAG_PER_INSTANCE 1
|
#define RPL_CONF_MAX_DAG_PER_INSTANCE 1
|
||||||
|
|
|
@ -150,10 +150,10 @@
|
||||||
|
|
||||||
/* configure number of neighbors and routes */
|
/* configure number of neighbors and routes */
|
||||||
#ifndef NBR_TABLE_CONF_MAX_NEIGHBORS
|
#ifndef NBR_TABLE_CONF_MAX_NEIGHBORS
|
||||||
#define NBR_TABLE_CONF_MAX_NEIGHBORS 20
|
#define NBR_TABLE_CONF_MAX_NEIGHBORS 16
|
||||||
#endif /* NBR_TABLE_CONF_MAX_NEIGHBORS */
|
#endif /* NBR_TABLE_CONF_MAX_NEIGHBORS */
|
||||||
#ifndef UIP_CONF_MAX_ROUTES
|
#ifndef UIP_CONF_MAX_ROUTES
|
||||||
#define UIP_CONF_MAX_ROUTES 20
|
#define UIP_CONF_MAX_ROUTES 16
|
||||||
#endif /* UIP_CONF_MAX_ROUTES */
|
#endif /* UIP_CONF_MAX_ROUTES */
|
||||||
|
|
||||||
#define UIP_CONF_ND6_SEND_RA 0
|
#define UIP_CONF_ND6_SEND_RA 0
|
||||||
|
|
|
@ -512,10 +512,10 @@ typedef uint32_t rtimer_clock_t;
|
||||||
#define UIP_CONF_ND6_RETRANS_TIMER 10000
|
#define UIP_CONF_ND6_RETRANS_TIMER 10000
|
||||||
|
|
||||||
#ifndef NBR_TABLE_CONF_MAX_NEIGHBORS
|
#ifndef NBR_TABLE_CONF_MAX_NEIGHBORS
|
||||||
#define NBR_TABLE_CONF_MAX_NEIGHBORS 20
|
#define NBR_TABLE_CONF_MAX_NEIGHBORS 16
|
||||||
#endif
|
#endif
|
||||||
#ifndef UIP_CONF_MAX_ROUTES
|
#ifndef UIP_CONF_MAX_ROUTES
|
||||||
#define UIP_CONF_MAX_ROUTES 20
|
#define UIP_CONF_MAX_ROUTES 16
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* uIP */
|
/* uIP */
|
||||||
|
|
|
@ -289,9 +289,9 @@ lostMsgs = 0;
|
||||||

|

|
||||||
// we check that we got up to at least message 62 and
|
// we check that we got up to at least message 62 and
|
||||||
// (the simulation is 4000000ms = 66 minutes long)
|
// (the simulation is 4000000ms = 66 minutes long)
|
||||||
// that we did not lose anything since 34
|
// that we did not lose anything since 45
|
||||||
// (the sink is back at 2000000ms = 33 minutes)
|
// (the sink is back at 2000000ms = 33 minutes)
|
||||||
TIMEOUT(4000000, if(lastMsg >= 62 && lastMissed <= 34) { log.testOK(); } );
|
TIMEOUT(4000000, if(lastMsg >= 62 && lastMissed <= 45) { log.testOK(); } );
|
||||||

|

|
||||||
lastMsg = -1;
|
lastMsg = -1;
|
||||||
lastMissed = -1;
|
lastMissed = -1;
|
||||||
|
|
Loading…
Reference in a new issue