Contiki 3.x
link-stats.c
1 /*
2  * Copyright (c) 2015, SICS Swedish ICT.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the Institute nor the names of its contributors
14  * may be used to endorse or promote products derived from this software
15  * without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  *
30  * Authors: Simon Duquennoy <simonduq@sics.se>
31  */
32 
33 #include "contiki.h"
34 #include "sys/clock.h"
35 #include "net/packetbuf.h"
36 #include "net/nbr-table.h"
37 #include "net/link-stats.h"
38 #include <stdio.h>
39 
40 #define DEBUG 0
41 #if DEBUG
42 #define PRINTF(...) printf(__VA_ARGS__)
43 #else
44 #define PRINTF(...)
45 #endif
46 
47 /* Half time for the freshness counter, in minutes */
48 #define FRESHNESS_HALF_LIFE 20
49 /* Statistics are fresh if the freshness counter is FRESHNESS_TARGET or more */
50 #define FRESHNESS_TARGET 4
51 /* Maximum value for the freshness counter */
52 #define FRESHNESS_MAX 16
53 /* Statistics with no update in FRESHNESS_EXPIRATION_TIMEOUT is not fresh */
54 #define FRESHNESS_EXPIRATION_TIME (10 * 60 * CLOCK_SECOND)
55 
56 /* EWMA (exponential moving average) used to maintain statistics over time */
57 #define EWMA_SCALE 100
58 #define EWMA_ALPHA 15
59 #define EWMA_BOOTSTRAP_ALPHA 30
60 
61 /* ETX fixed point divisor. 128 is the value used by RPL (RFC 6551 and RFC 6719) */
62 #define ETX_DIVISOR LINK_STATS_ETX_DIVISOR
63 /* Number of Tx used to update the ETX EWMA in case of no-ACK */
64 #define ETX_NOACK_PENALTY 10
65 /* Initial ETX value */
66 #define ETX_INIT 2
67 
68 /* Per-neighbor link statistics table */
69 NBR_TABLE(struct link_stats, link_stats);
70 
71 /* Called every FRESHNESS_HALF_LIFE minutes */
72 struct ctimer periodic_timer;
73 
74 /* Used to initialize ETX before any transmission occurs. In order to
75  * infer the initial ETX from the RSSI of previously received packets, use: */
76 /* #define LINK_STATS_CONF_INIT_ETX(stats) guess_etx_from_rssi(stats) */
77 
78 #ifdef LINK_STATS_CONF_INIT_ETX
79 #define LINK_STATS_INIT_ETX(stats) LINK_STATS_CONF_INIT_ETX(stats)
80 #else /* LINK_STATS_INIT_ETX */
81 #define LINK_STATS_INIT_ETX(stats) (ETX_INIT * ETX_DIVISOR)
82 #endif /* LINK_STATS_INIT_ETX */
83 
84 /*---------------------------------------------------------------------------*/
85 /* Returns the neighbor's link stats */
86 const struct link_stats *
87 link_stats_from_lladdr(const linkaddr_t *lladdr)
88 {
89  return nbr_table_get_from_lladdr(link_stats, lladdr);
90 }
91 /*---------------------------------------------------------------------------*/
92 /* Are the statistics fresh? */
93 int
94 link_stats_is_fresh(const struct link_stats *stats)
95 {
96  return (stats != NULL)
97  && clock_time() - stats->last_tx_time < FRESHNESS_EXPIRATION_TIME
98  && stats->freshness >= FRESHNESS_TARGET;
99 }
100 /*---------------------------------------------------------------------------*/
101 uint16_t
102 guess_etx_from_rssi(const struct link_stats *stats)
103 {
104  if(stats != NULL) {
105  if(stats->rssi == 0) {
106  return ETX_INIT * ETX_DIVISOR;
107  } else {
108  /* A rough estimate of PRR from RSSI, as a linear function where:
109  * RSSI >= -60 results in PRR of 1
110  * RSSI <= -90 results in PRR of 0
111  * prr = (bounded_rssi - RSSI_LOW) / (RSSI_DIFF)
112  * etx = ETX_DIVOSOR / ((bounded_rssi - RSSI_LOW) / RSSI_DIFF)
113  * etx = (RSSI_DIFF * ETX_DIVOSOR) / (bounded_rssi - RSSI_LOW)
114  * */
115 #define ETX_INIT_MAX 3
116 #define RSSI_HIGH -60
117 #define RSSI_LOW -90
118 #define RSSI_DIFF (RSSI_HIGH - RSSI_LOW)
119  uint16_t etx;
120  int16_t bounded_rssi = stats->rssi;
121  bounded_rssi = MIN(bounded_rssi, RSSI_HIGH);
122  bounded_rssi = MAX(bounded_rssi, RSSI_LOW + 1);
123  etx = RSSI_DIFF * ETX_DIVISOR / (bounded_rssi - RSSI_LOW);
124  return MIN(etx, ETX_INIT_MAX * ETX_DIVISOR);
125  }
126  }
127  return 0xffff;
128 }
129 /*---------------------------------------------------------------------------*/
130 /* Packet sent callback. Updates stats for transmissions to lladdr */
131 void
132 link_stats_packet_sent(const linkaddr_t *lladdr, int status, int numtx)
133 {
134  struct link_stats *stats;
135  uint16_t packet_etx;
136  uint8_t ewma_alpha;
137 
138  if(status != MAC_TX_OK && status != MAC_TX_NOACK) {
139  /* Do not penalize the ETX when collisions or transmission errors occur. */
140  return;
141  }
142 
143  stats = nbr_table_get_from_lladdr(link_stats, lladdr);
144  if(stats == NULL) {
145  /* Add the neighbor */
146  stats = nbr_table_add_lladdr(link_stats, lladdr, NBR_TABLE_REASON_LINK_STATS, NULL);
147  if(stats != NULL) {
148  stats->etx = LINK_STATS_INIT_ETX(stats);
149  } else {
150  return; /* No space left, return */
151  }
152  }
153 
154  /* Update last timestamp and freshness */
155  stats->last_tx_time = clock_time();
156  stats->freshness = MIN(stats->freshness + numtx, FRESHNESS_MAX);
157 
158  /* ETX used for this update */
159  packet_etx = ((status == MAC_TX_NOACK) ? ETX_NOACK_PENALTY : numtx) * ETX_DIVISOR;
160  /* ETX alpha used for this update */
161  ewma_alpha = link_stats_is_fresh(stats) ? EWMA_ALPHA : EWMA_BOOTSTRAP_ALPHA;
162 
163  /* Compute EWMA and update ETX */
164  stats->etx = ((uint32_t)stats->etx * (EWMA_SCALE - ewma_alpha) +
165  (uint32_t)packet_etx * ewma_alpha) / EWMA_SCALE;
166 }
167 /*---------------------------------------------------------------------------*/
168 /* Packet input callback. Updates statistics for receptions on a given link */
169 void
170 link_stats_input_callback(const linkaddr_t *lladdr)
171 {
172  struct link_stats *stats;
173  int16_t packet_rssi = packetbuf_attr(PACKETBUF_ATTR_RSSI);
174 
175  stats = nbr_table_get_from_lladdr(link_stats, lladdr);
176  if(stats == NULL) {
177  /* Add the neighbor */
178  stats = nbr_table_add_lladdr(link_stats, lladdr, NBR_TABLE_REASON_LINK_STATS, NULL);
179  if(stats != NULL) {
180  /* Initialize */
181  stats->rssi = packet_rssi;
182  stats->etx = LINK_STATS_INIT_ETX(stats);
183  }
184  return;
185  }
186 
187  /* Update RSSI EWMA */
188  stats->rssi = ((int32_t)stats->rssi * (EWMA_SCALE - EWMA_ALPHA) +
189  (int32_t)packet_rssi * EWMA_ALPHA) / EWMA_SCALE;
190 }
191 /*---------------------------------------------------------------------------*/
192 /* Periodic timer called every FRESHNESS_HALF_LIFE minutes */
193 static void
194 periodic(void *ptr)
195 {
196  /* Age (by halving) freshness counter of all neighbors */
197  struct link_stats *stats;
198  ctimer_reset(&periodic_timer);
199  for(stats = nbr_table_head(link_stats); stats != NULL; stats = nbr_table_next(link_stats, stats)) {
200  stats->freshness >>= 1;
201  }
202 }
203 /*---------------------------------------------------------------------------*/
204 /* Initializes link-stats module */
205 void
206 link_stats_init(void)
207 {
208  nbr_table_register(link_stats, NULL);
209  ctimer_set(&periodic_timer, 60 * CLOCK_SECOND * FRESHNESS_HALF_LIFE,
210  periodic, NULL);
211 }
The MAC layer deferred the transmission for a later time.
Definition: mac.h:86
CCIF clock_time_t clock_time(void)
Get the current clock time.
Definition: clock.c:41
Header file for the Rime buffer (packetbuf) management
The MAC layer transmission was OK.
Definition: mac.h:79
#define NULL
The null pointer.
#define CLOCK_SECOND
A second, measured in system clock time.
Definition: clock.h:82
void ctimer_set(struct ctimer *c, clock_time_t t, void(*f)(void *), void *ptr)
Set a callback timer.
Definition: ctimer.c:99
void ctimer_reset(struct ctimer *c)
Reset a callback timer with the same interval as was previously set.
Definition: ctimer.c:125