Add a very sleepy CC26xx/CC13xx demo

This demonstraties how to combine CC13xx tick suppression, RPL leaf mode and turning off ContikiMAC duty cycling to build an extremely low-consuming firmware.
This commit is contained in:
Jonas Olsson 2015-08-16 16:35:45 +01:00
parent 72b586cb7d
commit bad7eb2bc8
6 changed files with 579 additions and 0 deletions

View file

@ -0,0 +1,12 @@
DEFINES+=PROJECT_CONF_H=\"project-conf.h\"
CONTIKI_PROJECT = very-sleepy-demo
all: $(CONTIKI_PROJECT)
CONTIKI_WITH_IPV6 = 1
APPS += er-coap
APPS += rest-engine
CONTIKI = ../../..
include $(CONTIKI)/Makefile.include

View file

@ -0,0 +1 @@
TARGET = srf06-cc26xx

View file

@ -0,0 +1,91 @@
# CC13xx/CC26xx Very Sleepy Demo
This example demonstrates a way of deploying a very low-consuming, very sleepy
node. The node has two modes of operation:
* Normal: ContikiMAC duty-cycles the radio as usual. The node is reachable.
* Very Sleepy: Radio cycling mostly off, except when we need to perform network
maintenance tasks. In this mode, the node is unreachable for most of the time.
The node will operate in RPL leaf mode. This means that it will be reachable
downwards, but it will not advertise the DODAG and it will not participate in
routing.
After booting, the node will enter "normal" mode.
The node exposes an OBSERVEable CoAP resource. It will notify subscribers with
a new value for this resource every `interval` seconds. It will then stay in
normal mode for `duration` seconds. During this time window, it will be
reachable over the network in order to e.g. receive a new configuration.
When this time window expires, the node will switch back to very sleepy mode.
This will only happen if very sleepy mode has been enabled by setting `mode=1`
as per the instructions below.
When the node is duty-cycling the radio, either because it is in normal mode or
because network maintenance is taking place, it will keep its green LED on thus
providing an indication that it is reachable.
A normal mode stint can be manually triggered by pressing the left button.
## Requirements
To run this example you will need:
* A border router operating with the same RDC, same channel, same radio mode
(e.g. IEEE or sub-ghz), same PAN ID. Alternatively, you can
use [6lbr](https://github.com/cetic/6lbr) with a suitable slip-radio.
* The [Copper (Cu)](https://addons.mozilla.org/en-US/firefox/addon/copper-270430/)
addon for Firefox
## Configuration
To configure the node, send a CoAP POST message to the `very_sleepy_config`
resource. The POST message's payload must specify _at least one_ of:
* `mode=0|1`: Send `mode=1` to enable very sleepy mode, `mode=0` to disable it.
* `interval=n` where `n` is the number of seconds between two consecutive normal
mode periods. This interval also dictates the OBSERVEr notification period.
* `duration=n` where `n` is the number of seconds that the node will stay in
normal mode before dropping to very sleepy mode. This value is only relevant
if `mode==1`.
A POST request must contain at least one of the above, but they are otherwise
all optional. So, for example, a POST may simply specify `interval=n`. To send
multiple values, delimit them with `&`. So you can send something like
`mode=1&interval=60&duration=20`
The current running configuration can be retrieved by sending a GET request to
the same CoAP resource.
## Running the example
* Deploy your border router or 6lbr
* Turn on the very sleepy node.
* Fire up the Copper addon
* Select `.well-known/core` and hit `GET`
* Configure very sleepy operation:
* Select the `very_sleepy_config` resource
* In the `Outgoing` pane, type your POST payload as per the instructions
above. For example, you can type: `mode=1&interval=30&duration=10`
* Hit `POST`
* Select the `sen/readings` resource and hit `OBSERVE`
## Caveats
If you click on a resource in the Copper resources tree while you are observing
a different resource, the OBSERVEr for the latter will be stopped without
notifying the CoAP server. This will result in the server sending out OBSERVE
notifications that will be responded to with port unreachable ICMPv6 messages.
This will continue for quite a while, until the server detects that the
OBSERVEr has been lost (a test currently performed once every 20 notifications).
In order to prevent this from happening, hit the "Cancel" button for the
OBSERVE before switching views to a different resource. This will unregister
the observer.
In very sleepy mode, the radio is not truly always off. The contiki core needs
to perform other periodic tasks in order to maintain network connectivity. For
that reason, this example will allow the radio to turn on periodically even
while in very sleepy mode. Thus, you may see that the node becomes briefly
reachable every now and then. However, do not count on those periods of
reachability to perform any tasks, as they will be brief and will be disrupted
without warning.

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2015, Texas Instruments Incorporated - http://www.ti.com/
* 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 copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
* COPYRIGHT HOLDER 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.
*/
/*---------------------------------------------------------------------------*/
#ifndef PROJECT_CONF_H_
#define PROJECT_CONF_H_
/*---------------------------------------------------------------------------*/
/* Change to match your configuration */
#define IEEE802154_CONF_PANID 0xABCD
#define RF_CORE_CONF_CHANNEL 25
/*---------------------------------------------------------------------------*/
/* For very sleepy operation */
#define RF_BLE_CONF_ENABLED 0
#define UIP_DS6_CONF_PERIOD CLOCK_SECOND
#define UIP_CONF_TCP 0
#define RPL_CONF_LEAF_ONLY 1
/*
* We'll fail without RPL probing, so turn it on explicitly even though it's
* on by default
*/
#define RPL_CONF_WITH_PROBING 1
/*---------------------------------------------------------------------------*/
#endif /* PROJECT_CONF_H_ */
/*---------------------------------------------------------------------------*/

View file

@ -0,0 +1,423 @@
/*
* Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/
* 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 copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
* COPYRIGHT HOLDER 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.
*/
/*---------------------------------------------------------------------------*/
#include "contiki.h"
#include "sys/etimer.h"
#include "sys/stimer.h"
#include "sys/process.h"
#include "dev/leds.h"
#include "dev/watchdog.h"
#include "button-sensor.h"
#include "batmon-sensor.h"
#include "board-peripherals.h"
#include "net/netstack.h"
#include "net/ipv6/uip-ds6-nbr.h"
#include "net/ipv6/uip-ds6-route.h"
#include "net/rpl/rpl.h"
#include "net/rpl/rpl-private.h"
#include "rest-engine.h"
#include "er-coap.h"
#include "ti-lib.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
/*---------------------------------------------------------------------------*/
/* Normal mode duration params in seconds */
#define NORMAL_OP_DURATION_DEFAULT 10
#define NORMAL_OP_DURATION_MIN 10
#define NORMAL_OP_DURATION_MAX 60
/*---------------------------------------------------------------------------*/
/* Observer notification period params in seconds */
#define PERIODIC_INTERVAL_DEFAULT 30
#define PERIODIC_INTERVAL_MIN 30
#define PERIODIC_INTERVAL_MAX 86400 /* 1 day */
/*---------------------------------------------------------------------------*/
#define VERY_SLEEPY_MODE_OFF 0
#define VERY_SLEEPY_MODE_ON 1
/*---------------------------------------------------------------------------*/
#define MAC_CAN_BE_TURNED_OFF 0
#define MAC_MUST_STAY_ON 1
#define KEEP_MAC_ON_MIN_PERIOD 10 /* secs */
/*---------------------------------------------------------------------------*/
#define PERIODIC_INTERVAL CLOCK_SECOND
/*---------------------------------------------------------------------------*/
#define POST_STATUS_BAD 0x80
#define POST_STATUS_HAS_MODE 0x40
#define POST_STATUS_HAS_DURATION 0x20
#define POST_STATUS_HAS_INTERVAL 0x10
#define POST_STATUS_NONE 0x00
/*---------------------------------------------------------------------------*/
typedef struct sleepy_config_s {
unsigned long interval;
unsigned long duration;
uint8_t mode;
} sleepy_config_t;
sleepy_config_t config;
/*---------------------------------------------------------------------------*/
#define STATE_NORMAL 0
#define STATE_NOTIFY_OBSERVERS 1
#define STATE_VERY_SLEEPY 2
/*---------------------------------------------------------------------------*/
static struct stimer st_duration;
static struct stimer st_interval;
static struct stimer st_min_mac_on_duration;
static struct etimer et_periodic;
static process_event_t event_new_config;
static uint8_t state;
/*---------------------------------------------------------------------------*/
const char *not_supported_msg = "Supported:text/plain,application/json";
/*---------------------------------------------------------------------------*/
PROCESS(very_sleepy_demo_process, "CC13xx/CC26xx very sleepy process");
AUTOSTART_PROCESSES(&very_sleepy_demo_process);
/*---------------------------------------------------------------------------*/
static void
readings_get_handler(void *request, void *response, uint8_t *buffer,
uint16_t preferred_size, int32_t *offset)
{
unsigned int accept = -1;
int temp;
int voltage;
if(request != NULL) {
REST.get_header_accept(request, &accept);
}
temp = batmon_sensor.value(BATMON_SENSOR_TYPE_TEMP);
voltage = batmon_sensor.value(BATMON_SENSOR_TYPE_VOLT);
if(accept == -1 || accept == REST.type.APPLICATION_JSON) {
REST.set_header_content_type(response, REST.type.APPLICATION_JSON);
snprintf((char *)buffer, REST_MAX_CHUNK_SIZE,
"{\"temp\":{\"v\":%d,\"u\":\"C\"},"
"\"voltage\":{\"v\":%d,\"u\":\"mV\"}}",
temp, (voltage * 125) >> 5);
REST.set_response_payload(response, buffer, strlen((char *)buffer));
} else if(accept == REST.type.TEXT_PLAIN) {
REST.set_header_content_type(response, REST.type.TEXT_PLAIN);
snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, "Temp=%dC, Voltage=%dmV",
temp, (voltage * 125) >> 5);
REST.set_response_payload(response, buffer, strlen((char *)buffer));
} else {
REST.set_response_status(response, REST.status.NOT_ACCEPTABLE);
REST.set_response_payload(response, not_supported_msg,
strlen(not_supported_msg));
}
}
/*---------------------------------------------------------------------------*/
RESOURCE(readings_resource, "title=\"Sensor Readings\";obs",
readings_get_handler, NULL, NULL, NULL);
/*---------------------------------------------------------------------------*/
static void
conf_get_handler(void *request, void *response, uint8_t *buffer,
uint16_t preferred_size, int32_t *offset)
{
unsigned int accept = -1;
if(request != NULL) {
REST.get_header_accept(request, &accept);
}
if(accept == -1 || accept == REST.type.APPLICATION_JSON) {
REST.set_header_content_type(response, REST.type.APPLICATION_JSON);
snprintf((char *)buffer, REST_MAX_CHUNK_SIZE,
"{\"config\":{\"mode\":%u,\"duration\":%lu,\"interval\":%lu}}",
config.mode, config.duration, config.interval);
REST.set_response_payload(response, buffer, strlen((char *)buffer));
} else if(accept == REST.type.TEXT_PLAIN) {
REST.set_header_content_type(response, REST.type.TEXT_PLAIN);
snprintf((char *)buffer, REST_MAX_CHUNK_SIZE,
"Mode=%u, Duration=%lusecs, Interval=%lusecs",
config.mode, config.duration, config.interval);
REST.set_response_payload(response, buffer, strlen((char *)buffer));
} else {
REST.set_response_status(response, REST.status.NOT_ACCEPTABLE);
REST.set_response_payload(response, not_supported_msg,
strlen(not_supported_msg));
}
}
/*---------------------------------------------------------------------------*/
static void
conf_post_handler(void *request, void *response, uint8_t *buffer,
uint16_t preferred_size, int32_t *offset)
{
const char *ptr = NULL;
char tmp_buf[16];
unsigned long interval = 0;
unsigned long duration = 0;
uint8_t mode = VERY_SLEEPY_MODE_OFF;
uint8_t post_status = POST_STATUS_NONE;
int rv;
rv = REST.get_post_variable(request, "mode", &ptr);
if(rv && rv < 16) {
memset(tmp_buf, 0, sizeof(tmp_buf));
memcpy(tmp_buf, ptr, rv);
rv = atoi(tmp_buf);
if(rv == 1) {
mode = VERY_SLEEPY_MODE_ON;
post_status |= POST_STATUS_HAS_MODE;
} else if(rv == 0) {
mode = VERY_SLEEPY_MODE_OFF;
post_status |= POST_STATUS_HAS_MODE;
} else {
post_status = POST_STATUS_BAD;
}
}
rv = REST.get_post_variable(request, "duration", &ptr);
if(rv && rv < 16) {
memset(tmp_buf, 0, sizeof(tmp_buf));
memcpy(tmp_buf, ptr, rv);
rv = atoi(tmp_buf);
duration = (unsigned long)rv;
if(duration < NORMAL_OP_DURATION_MIN || duration > NORMAL_OP_DURATION_MAX) {
post_status = POST_STATUS_BAD;
} else {
post_status |= POST_STATUS_HAS_DURATION;
}
}
rv = REST.get_post_variable(request, "interval", &ptr);
if(rv && rv < 16) {
memset(tmp_buf, 0, sizeof(tmp_buf));
memcpy(tmp_buf, ptr, rv);
rv = atoi(tmp_buf);
interval = (unsigned long)rv;
if(interval < PERIODIC_INTERVAL_MIN || interval > PERIODIC_INTERVAL_MAX) {
post_status = POST_STATUS_BAD;
} else {
post_status |= POST_STATUS_HAS_INTERVAL;
}
}
if((post_status & POST_STATUS_BAD) == POST_STATUS_BAD ||
post_status == POST_STATUS_NONE) {
REST.set_response_status(response, REST.status.BAD_REQUEST);
snprintf((char *)buffer, REST_MAX_CHUNK_SIZE,
"mode=0|1&duration=[%u,%u]&interval=[%u,%u]",
NORMAL_OP_DURATION_MIN, NORMAL_OP_DURATION_MAX,
PERIODIC_INTERVAL_MIN, PERIODIC_INTERVAL_MAX);
REST.set_response_payload(response, buffer, strlen((char *)buffer));
return;
}
/* Values are sane. Update the config and notify the process */
if(post_status & POST_STATUS_HAS_MODE) {
config.mode = mode;
}
if(post_status & POST_STATUS_HAS_INTERVAL) {
config.interval = interval;
}
if(post_status & POST_STATUS_HAS_DURATION) {
config.duration = duration;
}
process_post(&very_sleepy_demo_process, event_new_config, NULL);
}
/*---------------------------------------------------------------------------*/
RESOURCE(very_sleepy_conf,
"title=\"Very sleepy conf: "
"GET|POST mode=0|1&interval=<secs>&duration=<secs>\";rt=\"Control\"",
conf_get_handler, conf_post_handler, NULL, NULL);
/*---------------------------------------------------------------------------*/
/*
* If our preferred parent is not NBR_REACHABLE in the ND cache, NUD will send
* a unicast NS and wait for NA. If NA fails then the neighbour will be removed
* from the ND cache and the default route will be deleted. To prevent this,
* keep the MAC on until the parent becomes NBR_REACHABLE. We also keep the MAC
* on if we are about to do RPL probing.
*
* In all cases, the radio will be locked on for KEEP_MAC_ON_MIN_PERIOD secs
*/
static uint8_t
keep_mac_on(void)
{
uip_ds6_nbr_t *nbr;
uint8_t rv = MAC_CAN_BE_TURNED_OFF;
if(!stimer_expired(&st_min_mac_on_duration)) {
return MAC_MUST_STAY_ON;
}
#if RPL_WITH_PROBING
/* Determine if we are about to send a RPL probe */
if(CLOCK_LT(etimer_expiration_time(
&rpl_get_default_instance()->probing_timer.etimer),
(clock_time() + PERIODIC_INTERVAL))) {
rv = MAC_MUST_STAY_ON;
}
#endif
/* It's OK to pass a NULL pointer, the callee checks and returns NULL */
nbr = uip_ds6_nbr_lookup(uip_ds6_defrt_choose());
if(nbr == NULL) {
/* We don't have a default route, or it's not reachable (NUD likely). */
rv = MAC_MUST_STAY_ON;
} else {
if(nbr->state != NBR_REACHABLE) {
rv = MAC_MUST_STAY_ON;
}
}
if(rv == MAC_MUST_STAY_ON && stimer_expired(&st_min_mac_on_duration)) {
stimer_set(&st_min_mac_on_duration, KEEP_MAC_ON_MIN_PERIOD);
}
return rv;
}
/*---------------------------------------------------------------------------*/
static void
switch_to_normal(void)
{
state = STATE_NOTIFY_OBSERVERS;
/*
* Stay in normal mode for 'duration' secs.
* Transition back to normal in 'interval' secs, _including_ 'duration'
*/
stimer_set(&st_duration, config.duration);
stimer_set(&st_interval, config.interval);
}
/*---------------------------------------------------------------------------*/
static void
switch_to_very_sleepy(void)
{
state = STATE_VERY_SLEEPY;
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(very_sleepy_demo_process, ev, data)
{
uint8_t mac_keep_on;
PROCESS_BEGIN();
SENSORS_ACTIVATE(batmon_sensor);
config.mode = VERY_SLEEPY_MODE_OFF;
config.interval = PERIODIC_INTERVAL_DEFAULT;
config.duration = NORMAL_OP_DURATION_DEFAULT;
state = STATE_NORMAL;
event_new_config = process_alloc_event();
rest_init_engine();
readings_resource.flags += IS_OBSERVABLE;
rest_activate_resource(&readings_resource, "sen/readings");
rest_activate_resource(&very_sleepy_conf, "very_sleepy_config");
printf("Very Sleepy Demo Process\n");
switch_to_normal();
etimer_set(&et_periodic, PERIODIC_INTERVAL);
while(1) {
PROCESS_YIELD();
if(ev == sensors_event && data == &button_left_sensor) {
switch_to_normal();
}
if(ev == event_new_config) {
stimer_set(&st_interval, config.interval);
stimer_set(&st_duration, config.duration);
}
if((ev == PROCESS_EVENT_TIMER && data == &et_periodic) ||
(ev == sensors_event && data == &button_left_sensor) ||
(ev == event_new_config)) {
/*
* Determine if the stack is about to do essential network maintenance
* and, if so, keep the MAC layer on
*/
mac_keep_on = keep_mac_on();
if(mac_keep_on == MAC_MUST_STAY_ON || state != STATE_VERY_SLEEPY) {
leds_on(LEDS_GREEN);
NETSTACK_MAC.on();
}
/*
* Next, switch between normal and very sleepy mode depending on config,
* send notifications to observers as required.
*/
if(state == STATE_NOTIFY_OBSERVERS) {
REST.notify_subscribers(&readings_resource);
state = STATE_NORMAL;
}
if(state == STATE_NORMAL) {
if(stimer_expired(&st_duration)) {
stimer_set(&st_duration, config.duration);
if(config.mode == VERY_SLEEPY_MODE_ON) {
switch_to_very_sleepy();
}
}
} else if(state == STATE_VERY_SLEEPY) {
if(stimer_expired(&st_interval)) {
switch_to_normal();
}
}
if(mac_keep_on == MAC_CAN_BE_TURNED_OFF && state == STATE_VERY_SLEEPY) {
leds_off(LEDS_GREEN);
NETSTACK_MAC.off(0);
} else {
leds_on(LEDS_GREEN);
NETSTACK_MAC.on();
}
/* Schedule next pass */
etimer_set(&et_periodic, PERIODIC_INTERVAL);
}
}
PROCESS_END();
}
/*---------------------------------------------------------------------------*/

View file

@ -8,6 +8,7 @@ webserver-ipv6/ev-aducrf101mkxz \
ipv6/multicast/ev-aducrf101mkxz \
cc2538dk/sniffer/ev-aducrf101mkxz \
cc26xx/cc26xx-web-demo/srf06-cc26xx \
cc26xx/very-sleepy-demo/srf06-cc26xx \
hello-world/cc2538dk \
ipv6/rpl-border-router/cc2538dk \
er-rest-example/cc2538dk \