Skip to content

Latest commit

 

History

History
2201 lines (1654 loc) · 88.3 KB

4.asc

File metadata and controls

2201 lines (1654 loc) · 88.3 KB

Wireless with Contiki

In the previous section we covered some of the core features of Contiki, basics of sensors and a general overview of how the applications are built, programmed and simulated in Contiki. This section introduces the wireless communication, details about radios, and in general how to configure our platforms.

Addressing and Radio Frequency basics

The very first step is understanding how our platform is configured.

Each platform implements its own set of default values and configurations, to be used by underlying modules like the radio, the serial port, etc.

The places to visit for the Zolertia platform are the following:

  • Specific hardware settings: parameters such as the default I2C pins, ADC channels, module-specific pin assignment and platform information can be found at platform/zoul/remote/dev/board.h and platform/z1/platform-conf.h.

  • Specific Contiki settings: UART settings, MAC driver, radio channel, IPv6, RIME and network buffer configuration, among others, can be found at platform/zoul/contiki-conf.h and platform/z1/contiki-conf.h.

As a general good practice, user configurable parameters are normally allowed to be overridden by the applications, this also serves as a guideline to discern which values can be changed by the casual user, from those meant to be changed only if you really know what you are doing. Below is an example:

#ifndef UART0_CONF_BAUD_RATE
#define UART0_CONF_BAUD_RATE 115200
#endif

By defining UART0_CONF_BAUD_RATE in our application’s project-conf.h, we can change the default 115200 baud rate. Notice that generally it is a good practice to add CONF to the user configurable parameters.

Tip

One of the most used tools is probably grep, a handy command to search for a text string in any document or location. One way to run this command is grep -lr "alinan" ., if executed at the root of our Contiki installation, it will list recursively all the files authored by Antonio Linan. This is a good way to check the location of a specific definition, if you are not using an IDE like Eclipse. Check man grep for more information.

For windows Astrogrep is a good option.

In the next section we will review the most notable parameters to configure, but as usual depending on your application and setup, the best way to ensure everything is properly set is by reviewing the specific platform configuration files, and modify or redefine accordingly.

Device addressing

To start working you must first define the Node ID of each node, this will be used to generate the mote’s MAC address and the IPv6 addresses (link-local and global).

RE-Mote addresses

The RE-Mote platform comes with two pre-saved MAC addresses stored in its internal flash memory, but the user can instead choose a hardcoded one. The following switches at platform/zoul/contiki-conf.h selects the chosen one.

/**
  * \name IEEE address configuration
  *
  * Used to generate our RIME & IPv6 address
  * @{
  */
/**
  * \brief Location of the IEEE address
  * 0 => Read from InfoPage,
  * 1 => Use a hardcoded address, configured by IEEE_ADDR_CONF_ADDRESS
  */
#ifndef IEEE_ADDR_CONF_HARDCODED
#define IEEE_ADDR_CONF_HARDCODED             0
#endif

/**
 * \brief Location of the IEEE address in the InfoPage when
 * IEEE_ADDR_CONF_HARDCODED is defined as 0
 * 0 => Use the primary address location
 * 1 => Use the secondary address location
 */
#ifndef IEEE_ADDR_CONF_USE_SECONDARY_LOCATION
#define IEEE_ADDR_CONF_USE_SECONDARY_LOCATION 0
#endif

If using your own hardcoded address, the following define can be overridden by the application:

#ifndef IEEE_ADDR_CONF_ADDRESS
#define IEEE_ADDR_CONF_ADDRESS { 0x00, 0x12, 0x4B, 0x00, 0x89, 0xAB, 0xCD, 0xEF }
#endif
Z1 mote addresses

Let’s use the ID from the mote list:

Reference  Device       Description
--------------------------------------------------
Z1RC3301   /dev/ttyUSB0 Silicon Labs Zolertia Z1

The node ID should be 3301 (decimal) if no previously saved node ID is found in the flash memory.

Let’s see how Contiki uses this to derive a full IPv6 and MAC address. At platforms/z1/contiki-z1-main.c

#ifdef SERIALNUM
  if(!node_id) {
    PRINTF("Node id is not set, using Z1 product ID\n");
    node_id = SERIALNUM;
  }
#endif
node_mac[0] = 0xc1; /* Hardcoded for Z1 */
node_mac[1] = 0x0c; /* Hardcoded for Revision C */
node_mac[2] = 0x00; /* Hardcoded to arbitrary even number so that the 802.15.4 MAC address is compatible with an Ethernet MAC address - byte 0 (byte 2 in the DS ID) */
node_mac[3] = 0x00; /* Hardcoded */
node_mac[4] = 0x00; /* Hardcoded */
node_mac[5] = 0x00; /* Hardcoded */
node_mac[6] = node_id >> 8;
node_mac[7] = node_id & 0xff;
}

So the mote should have the following addresses:

MAC c1:0c:00:00:00:00:0c:e5
Node id is set to 3301.
Tentative link-local IPv6 address fe80:0000:0000:0000:c30c:0000:0000:0ce5

Where 0xce5 is the hex value corresponding to 3301. The global address is only set when an IPv6 prefix is assigned (by now you should know this from earlier sections).

If instead you wish to have your own addressing scheme, you can edit the node_mac values at contiki-z1-main.c file. If you wish to replace the node id value obtained from the product id, you need to store a new one in the flash memory, luckily there is already an application to do so:

Go to examples/zolertia/z1 location and replace the 158 for your own required value:

make clean && make burn-nodeid.upload nodeid=158 nodemac=158 && make z1-reset && make login

You should see the following:

MAC c1:0c:00:00:00:00:0c:e5 Ref ID: 3301
Contiki-2.6-1803-g03f57ae started. Node id is set to 3301.
CSMA ContikiMAC, channel check rate 8 Hz, radio channel 26
Tentative link-local IPv6 address fe80:0000:0000:0000:c30c:0000:0000:0ce5
Starting 'Burn node id'
Burning node id 158
Restored node id 158

As you can see, now the node ID has been changed to 158, when you restart the mote you should see that the changes have been applied:

MAC c1:0c:00:00:00:00:00:9e Ref ID: 3301
Contiki-2.6-1803-g03f57ae started. Node id is set to 158.
CSMA ContikiMAC, channel check rate 8 Hz, radio channel 26
Tentative link-local IPv6 address fe80:0000:0000:0000:c30c:0000:0000:009e

Set the bandwidth and channel

The bandwidth and allowed channels depend on the operating frequency band. They will be determined by the spectrum regulation agency of the country, along with maximum transmitted power allowable. .The IEEE 802.15.4 standard

The IEEE 802.15.4 is a standard for wireless communication, it specifies the physical and media access control layers for low-rate wireless personal area networks (LR-WPANs).

The standard specifies the use of the 868-868.8 MHz (in Europe and many other countries), the 902-928 MHz (in United States, Canada, and some Latin America countries), or the world-wide 2.400-2.4835 GHz band part of the Industrial Scientific and Medical applications (ISM).

image013
Figure 1. IEEE 802.15.4 2.4 GHz regulation requirements (electronicdesign.com, 2013)

In practice the 2.4 GHz band is being heavily used due to its world-wide availability. The ZigBee proprietary protocol by the ZigBee alliance was one of the early adopters of IEEE 802.15.4. It did so leveraging the physical and MAC layer of IEEE 802.15.4, specifying on top additional routing and networking functionality to build mesh networks.

Quite recently the Thread Group has proposed its own simplified IPv6-based mesh networking protocol for connecting products around the home to each other, to the Internet and to the cloud.

image014
Figure 2. Thread layers and standards (Thread group, 2015)
Working at 2.4 GHz

As the 2.4 GHz band is also used by other technologies like WiFi and Bluetooth, this spectrum is shared and overlaps might occur. The Figure below shows the channel allocation of the 2.4 GHz IEEE 802.15.4, and the recommended channels to avoid interferences with other co-located devices. With the rise of the Bluetooth Low Energy, and the ubiquitous WiFi present in our lives, the selection of a proper operating channel is crucial in any deployment.

image001
Figure 3. Channel assignment

The default channel of the RE-Mote is defined as follows:

#ifndef CC2538_RF_CONF_CHANNEL
#define CC2538_RF_CONF_CHANNEL              26
#endif /* CC2538_RF_CONF_CHANNEL */

The Z1 mote defines its default channel as:

#ifdef RF_CHANNEL
#define CC2420_CONF_CHANNEL RF_CHANNEL
#endif

#ifndef CC2420_CONF_CHANNEL
#define CC2420_CONF_CHANNEL                 26
#endif /* CC2420_CONF_CHANNEL */

The radio channel can be defined from the application’s project-conf.h or the Makefile.

The radio drivers in Contiki are implemented to comply with the struct radio_driver in core/dev/radio.h. This abstraction allows to interact with the radio using a standardized API, independently of the radio hardware. The functions to set and read radio parameters are explained below.

/** Get a radio parameter value. */
radio_result_t (* get_value)(radio_param_t param, radio_value_t *value);

/** Set a radio parameter value. */
radio_result_t (* set_value)(radio_param_t param, radio_value_t value);

To change the channel from the application use RADIO_PARAM_CHANNEL as follows:

rd = NETSTACK_RADIO.set_value(RADIO_PARAM_CHANNEL, value);

Where value can be any value from 11 to 26, and rd will be either RADIO_RESULT_INVALID_VALUE or RADIO_RESULT_OK.

Working at 863-950 MHz

The RE-Mote has a dual 2.4 GHz and 863-950 MHz RF interface, which can be selected alternatively, or used simultaneously (the latter currently not supported in Contiki at the moment).

As default the RE-Mote uses the IEEE 802.15.4g mandatory mode for the 868 MHz band, configured for 2-GFSK modulation, 50 kbps data rate and with 33 channels available.

The RE-Mote uses the Texas Instruments CC1200 RF transceiver, referred to in Contiki as dev/cc1200. The default configuration file is located in dev/cc1200/cc1200-802154g-863-870-fsk-50kbps.c.

To change channels from the application we use the RF API:

rd = NETSTACK_RADIO.set_value(RADIO_PARAM_CHANNEL, value);

Where value can be any value from 11 to 26, and rd it will be either RADIO_RESULT_INVALID_VALUE or RADIO_RESULT_OK.

Set the transmission power

The radio frequency power transmission is that at the output of the transmitter before reaching the antenna. The higher the transmission power the higher the wireless range, but the power consumption usually increases as well. The range is heavily dependent on the frequency, the antenna used and its height above the ground.

Changing the transmission power for the Z1 (CC2420) and the RE-Mote (CC2538)

The RE-Mote platform uses the CC2538 built-in 2.4 GHz radio. As default the transmission power is set to 3 dBm (2 mW) in the cpu/cc2538/cc2538-rf.h header as shown below.

CC2538_RF_TX_POWER_RECOMMENDED 0xD5

This recommended value is taken from the SmartRF Studio.

Other values and its corresponding output power levels are shown in the next table.

Table 1. CC2538 Transmission power recommended values (from SmartRF Studio)
TX Power (dBm) Value

+7

0xFF

+5

0xED

+3

0xD5

+1

0xC5

0

0xB6

-1

0xB0

-3

0xA1

-5

0x91

-7

0x88

-9

0x72

-11

0x62

-13

0x58

-15

0x42

-24

0x00

Tip

As illogical as it may sound, there might be some reasons to reduce transmission power:

  • To reduce the power consumption.

  • To test a multi-hop network without placing the nodes too far (too much power can saturate the receiver).

  • To avoid interference among co-located networks in the same area.

The current consumption can go from 24 mA to 34 mA when changing the transmission power from 0 dBm to 7 dBm (AN125).

The Z1 mote uses the Texas Instrument CC2420 RF transceiver. As default the transmission power is set to 0 dBm (1 mW), which is the maximum allowed by the radio.

The available output power and its corresponding configuration values are listed in the table below, as well as the current consumption at each level.

Table 2. CC2420 Transmission power (CC2420 datasheet, page 51)
TX Power (dBm) Value mA

0

31

17.4

-1

27

16.5

-3

23

15.2

-5

19

13.9

-7

15

12.5

-10

11

11.2

-15

7

9.9

-25

3

8.5

For both platforms the transmission power can be changed with:

rd = NETSTACK_RADIO.set_value(RADIO_PARAM_TXPOWER, value);

Where value can be any value from the above table, and rd it will be either RADIO_RESULT_INVALID_VALUE or RADIO_RESULT_OK.

Changing the transmission power for the RE-Mote (CC1200)

As mentioned earlier, the RE-Mote has an on-board sub-1 GHz interface based on the CC1120 radio transceiver, configured to operate in the 863-950 MHz bands. The maximum transmission power allowed depends on the specific band and regulations in place, which can also impose limits on the maximum antenna gain, be sure to check the local regulations before changing the output power.

The regulations are country specific and out of the scope of this section.

The following values are taken from the SmartRF Studio, using default IEEE 802.15.4g ETSI compliant configuration.

Table 3. CC1200 Transmission power recommended values (from SmartRF Studio)
TX Power (dBm) Value

+14

0x7F

+13

0x7C

+12

0x7A

+11

0x78

+8

0x71

+6

0x6C

+4

0x68

+3

0x66

+2

0x63

+1

0x61

0

0x5F

-3

0x58

-6

0x51

-11

0x46

-24

0x42

-40

0x41

These values correspond to the CC1200_PA_CFG1 register.

As default the CC1200 driver in Contiki starts with the maximum transmission power, defined as follows:

/* The maximum output power in dBm */
#define RF_CFG_MAX_TXPOWER              CC1200_CONST_TX_POWER_MAX

The minimum and maximum allowed values are set in dev/cc1200/cc1200-const.h as shown below.

/* Output power in dBm */
/* Up to now we don't handle the special power levels PA_POWER_RAMP < 3, hence
 * the minimum tx power is -16. See update_txpower().
 */
#define CC1200_CONST_TX_POWER_MIN       (-16)
/*
 * Maximum output power will probably depend on the band we use due to
 * regulation issues
 */
#define CC1200_CONST_TX_POWER_MAX       14

The CC1200 driver calculates the proper CC1200_PA_CFG1 register value, so we need to pass as value argument the required transmission power.

rd = NETSTACK_RADIO.set_value(RADIO_PARAM_TXPOWER, value);

Where value can be any value from -14 to 16, and rd will be either RADIO_RESULT_INVALID_VALUE or RADIO_RESULT_OK.

Due to the changing environment conditions that normally affect the wireless systems, such as rain, interferences, obstacles, reflections, etc., measuring the wireless medium and links quality is important.

Checking the wireless medium should be done in three stages: before deploying your network, at deployment phase and later at network runtime, to ensure that the nodes create and select the best available routes.

Link Quality Estimation

Link Quality Estimation is an integral part of assuring reliability in wireless networks. Various link estimation metrics have been proposed to effectively measure the quality of wireless links.

image002
Figure 4. Link quality estimation process

The ETX metric, or expected transmission count, is a measure of the quality of a path between two nodes in a wireless packet data network. ETX is the number of expected transmissions of a packet necessary for it to be received without error at its destination. This number varies from one to infinity. An ETX of one indicates a perfect transmission medium, where an ETX of infinity represents a completely non-functional link. Note that ETX is an expected transmission count for a future event, as opposed to an actual count of a past events. It is hence a real number, generally not an integer.

ETX can be used as the routing metric. Routes with a lower metric are preferred. In a route that includes multiple hops, the metric is the sum of the ETX of the individual hops.

Below we describe how to read the LQI and RSSI to have a first approximation of the link conditions.

What is RSSI?

RSSI (Received Signal Strenght Indicator) is a generic radio receiver technology metric used internally in a wireless networking device to determine the amount of radio energy received in a given channel. The end-user will likely observe an RSSI value when measuring the signal strength of a wireless network through the use of a wireless network monitoring tool like Wireshark, Kismet or Inssider.

The image below shows how the Packet Reception Rate (PRR) dramatically decreases as the CC2420 RSSI values worsen.

image003
Figure 5. Packet reception rate vs RSSI

There is no standardized relationship of any particular physical parameter to the RSSI reading, Vendors and chipset makers provide their own accuracy, granularity, and range for the actual power (measured in mW or dBm) and the corresponding RSSI values.

There are 2 different types of RSSI readings available:

  • The first one is an indication of the amount of power present in the wireless medium at the given frequency and at given time. In the absence of any packet in the air, this will be the noise floor. This measurement is also used to decide if the medium is free, and available to send a packet. A high value could be due to interference or to the presence of a packet in the air.

  • The second measurement is performed only after a packet has been correctly decoded, and gives the strength of the packet received from a specific node.

The first measurement can be read using the radio API as follows:

rd = NETSTACK_RADIO.get_value(RADIO_PARAM_RSSI, value);

Where value is a variable passed as a pointer to store the RSSI value, and rd it will be either RADIO_RESULT_INVALID_VALUE or RADIO_RESULT_OK.

To read the RSSI value of a correctly decoded received packet, at the receive callback:

packetbuf_attr(PACKETBUF_ATTR_RSSI);

More information about the packetbuf attributes is available in core/net/packetbuf.h.

For the CC2420 radio frequency transceiver on the Z1 mote, the RSSI can range from 0 to -100, values close to 0 mean good links while values close to -100 are indicators of a bad link, which could be due to multiple factors such as distance, environment, obstacles, interferences, etc.

What is LQI?

LQI (Link Quality Indicator) is a digital value often provide by Chipset vendors as an indicator of how well a signal is demodulated, in terms of the strength and quality of the received packet, thus indicating a good or bad wireless medium.

The example below shows how the Packet Reception Rate decreases as the LQI decreases.

image004
Figure 6. Packet reception rate vs LQI

To read the LQI value we use the Radio API:

rd = NETSTACK_RADIO.get_value(PACKETBUF_ATTR_LINK_QUALITY, value);

Where value is a variable passed as a pointer to store the LQI value, and rd it will be either RADIO_RESULT_INVALID_VALUE or RADIO_RESULT_OK.

The CC2420 radio used by the Z1 mote typically ranges from 110 (indicates a maximum quality frame) to 50 (typically the lowest quality frames detectable by the transceiver).

Detailed information about the CC2538 LQI calculation is found in the CC2538 user guide.

Configure the MAC layer

MAC protocols

Medium Access Control (MAC) protocols describe the medium access adopted in a network, by establishing the rules that specify when a given node is allowed to transmit packets.

Protocols can be classified as contention-based or reservation-based protocols.

The first are based on Carrier Sensing for detecting medium activity and are prone to collisions and lower efficiency at heavy loads, but are easy to implement. The second group is efficient in terms of throughput and energy, but require precise synchronization and is less adaptable to dynamic traffic.

The medium access implementation in Contiki has 3 different layers: Framer, Radio Duty-Cycle (RDC) and Medium Access Control (MAC).

image015

The network layer can be accessed through the global variables NETSTACK_FRAMER, NETSTACK_RDC and NETSTACK_MAC, which are defined at compilation time.

The variables are located in core/net/netstack.h, and can be defined by each platform as default and overridden by applications.

MAC driver

Contiki provides two MAC drivers: CSMA and NullMAC

CSMA (Carrier-Sense Multiple Access) receives incoming packets from the RDC layer and uses the RDC layer to transmit packets. If the RDC layer or the radio layer detects that the medium is busy, the MAC layer may retransmit the packet at a later point in time. CSMA protocol keeps a list of packets sent to each of the neighbors and calculate statistics such as number of retransmissions, collisions, deferrals, etc. The medium access check is performed by the RDC driver.

NullMAC is a simple pass-through protocol. It calls the appropriate RDC functions.

As default both Z1 mote and RE-Mote uses the CSMA driver.

#ifndef NETSTACK_CONF_MAC
#define NETSTACK_CONF_MAC     csma_driver
#endif

Alternatively, a user can choose NullMAC as follow:

#define NETSTACK_CONF_MAC nullmac_driver

RDC driver

Radio Duty-Cycle (RDC) layer handles the sleep period of nodes. This layer decides when packets will be transmitted and ensures that nodes are awake when packets are to be received.

The implementation of Contiki’s RDC protocols are available in core/net/mac. The following RDC drivers are implemented: contikimac, xmac, lpp, nullrdc and sicslowmac. The implementation and details of the aforementioned RDC drivers are out of the scope of this chapter. The most commonly used is ContikiMAC. NullRDC is a pass-through layer that never switches the radio off.

#ifndef NETSTACK_CONF_RDC
#define NETSTACK_CONF_RDC contikimac_driver
#endif

RDC drivers try to keep the radio off as much as possible, periodically checking the wireless medium for radio activity. When activity is detected, the radio is kept on to check if it has to receive the packet, or it can go back to sleep.

The channel check rate is given in Hz, specifying the number of times the channel is checked per second, and the default channel check rate is 8 Hz. Channel check rates are given in powers of two and typical settings are 2, 4, 8, and 16 Hz.

#ifndef NETSTACK_CONF_RDC_CHANNEL_CHECK_RATE
#define NETSTACK_CONF_RDC_CHANNEL_CHECK_RATE 8
#endif

A packet must generally be retransmitted or "strobed" until the receiver is on and receives it. This increments the power consumption of the transmitter and increases the radio traffic, but the power savings at the receiver compensates for this and there is a net overall power saving.

One alternative to optimize the RDC is to enable "phase optimization", which delays strobing until just before the receiver is expected to be awake. This however requires a good time synchronization between the transmitter and the receiver (more details in RDC Phase Optimization). To enable phase optimization change the 0 below to one.

#define CONTIKIMAC_CONF_WITH_PHASE_OPTIMIZATION 0
#define WITH_FAST_SLEEP 1

Framer driver

The Framer driver is actually a set of functions to frame the data to be transmitted, and to parse the received data. The Framer implementations are located in core/net/mac, of which the most noticeable ones are framer-802154 and framer-nullmac.

In the RE-Mote platform the following configuration is the default:

#ifndef NETSTACK_CONF_FRAMER
#if NETSTACK_CONF_WITH_IPV6
#define NETSTACK_CONF_FRAMER  framer_802154
#else /* NETSTACK_CONF_WITH_IPV6 */
#define NETSTACK_CONF_FRAMER  contikimac_framer
#endif /* NETSTACK_CONF_WITH_IPV6 */
#endif /* NETSTACK_CONF_FRAMER */

Meaning that when IPv6 is used, the framer-802154 is selected, else the contikimac_framer is used (default one for the contikimac_driver).

The framer-nullmac framer should be used together with nullmac_driver (MAC layer). This simply fills in the 2 fields of nullmac_hdr, which are: receiver address and sender address.

The framer-802154 is implemented in core/net/mac/framer-802154.c. The driver frames the data in compliance to the IEEE 802.15.4 (2003) standard. The framer insert and extracts the data to the packetbuf structure.

IPv6 and Routing

One of Contiki’s most prominent feature is the support of IP protocols, being one of the first embedded operating systems to provide IPv6 support.

Alternatively Contiki also supports IPv4 and non-IP communication (Rime), however the remainder of this book will focus in IPv6. There is a good set of rime examples available at examples/rime. The RE-Mote zoul-demo.c most basic example at examples/zolertia/zoul uses rime as well.

IPv6

The uIP is an Open Source TCP/IP stack designed to be used even with tiny 8 and 16 bit microcontrollers. It was initially developed by Adam Dunkels while at the Swedish Institute of Computer Science (SICS), licensed under a BSD style license, and further developed by a wide group of developers.

The implementation details of the uIP/uIPv6 is out of the scope of this section. The remainder of this section explains the basic configurations at the platform and application level.

To enable IPv6 the following has to be defined, either in the application’s Makefile or in its project-conf.h file:

#define UIP_CONF_IPV6  1
#ifndef NBR_TABLE_CONF_MAX_NEIGHBORS
#define NBR_TABLE_CONF_MAX_NEIGHBORS        20
#endif
#ifndef UIP_CONF_MAX_ROUTES
#define UIP_CONF_MAX_ROUTES                 20
#endif

/* uIP */
#ifndef UIP_CONF_BUFFER_SIZE
#define UIP_CONF_BUFFER_SIZE              1300
#endif

#define UIP_CONF_IPV6_QUEUE_PKT              0
#define UIP_CONF_IPV6_CHECKS                 1
#define UIP_CONF_IPV6_REASSEMBLY             0
#define UIP_CONF_MAX_LISTENPORTS             8
Tip

Depending on your application and your platform you might eventually ran out of RAM memory, specially true for platforms with 8-10KB RAM. A good way to save RAM is to reduce the UIP_CONF_MAX_ROUTES and NBR_TABLE_CONF_MAX_NEIGHBORS number in your project-conf.h.

RPL

There are several routing flavors to chose, but ultimately all do the same thing: ensure that packets arrive at the right destination. This is done in different ways depending on factors such as the routing metric (how a route is qualified as better than others), whether the routing is done dynamically or statically, etc.

In Contiki the default routing protocol is RPL. Other protocols such as Ad hoc On-Demand Distance Vector (AODV) are out of the scope of this section.

The specifics of the RPL implementation are out of the scope of this section, we merely describe the common configurations and provide a brief introduction to RPL. For more details, check the RPL implementation at core/net/rpl.

What is RPL?

RPL is an IPv6 routing protocol for low power and lossy networks designed by the IETF Routing Over Low power and Lossy network (ROLL) group, used as the de facto routing protocol in Contiki. RPL is a proactive distance vector protocol, it starts finding the routes as soon as the RPL network is initialized.

image005
Figure 8. RPL in the protocol stack

It supports three traffic patterns:

  • Multipoint-to-point (MP2P)

  • Point-to-multipoint (P2MP)

  • Point-to-point (P2P)

RPL builds Destination Oriented DAGs (DODAGs) rooted towards one sink (DAG ROOT) identified by a unique identifier DODAGID. The DODAGs are optimized using an Objective Function (OF) metric identified by an Objective Code Point (OCP), which indicates the dynamic constraints and the metrics such as hop count, latency, expected transmission count, parents selection, energy consumption, etc. A rank number is assigned to each node which can be used to determine its relative position and distance to the root in the DODAG.

Within a given network, there may be multiple, logically independent RPL instances. An RPL node may belong to multiple RPL instances, and may act as a router in some and as a leaf in others. A set of multiple DODAGs can be in an RPL INSTANCE and a node can be a member of multiple RPL INSTANCEs, but can belong to at most one DODAG per DAG INSTANCE.

A trickle timer mechanism regulates DODAG Information Object (DIO) message transmissions, which are used to build and maintain upwards routes of the DODAG, advertising its RPL instance, DODAG ID, RANK and DODAG version number.

A node can request DODAG information by sending DODAG Information Solicitation messages (DIS), soliciting DIO messages from its neighborhoods to update its routing information and join an instance.

Nodes have to monitor DIO messages before joining a DODAG, and then join a DODAG by selecting a parent Node from its neighbors using its advertised latency, OF and RANK. Destination Advertisement Object (DAO) messages are used to maintain downward routes by selecting the preferred parent with lower rank and sending a packet to the DAG ROOT through each of the intermediate Nodes.

RPL has two mechanisms to repair the topology of the DODAG, one to avoid looping and allow nodes to join/rejoin, and other called global repair. Global repair is initiated at the DODAG ROOT by incrementing the DODAG Version Number to create a new DODAG Version.

More information about RPL can be found in RFC6550.

Routing support is enabled as default in the Z1 mote and RE-Mote platform. To enable routing the following has to be enabled:

#ifndef UIP_CONF_ROUTER
#define UIP_CONF_ROUTER  1
#endif

To enable RPL add the following to your application’s Makefile or its project-conf.h file.

#define UIP_CONF_IPV6_RPL  1

The following is the default configuration done in the RE-Mote:

/* ND and Routing */
#define UIP_CONF_ND6_SEND_RA        0  (1)
#define UIP_CONF_IP_FORWARD         0  (2)
#define RPL_CONF_STATS              0  (3)
  1. Disable sending routing advertisements

  2. Disable IP forwarding

  3. RPL Configuration statistics are disabled

The RPL_CONF_OF parameter configures the RPL objective function. The Minimum Rank with Hysteresis Objective Function (MRHOF) uses ETX as routing metric and it also has stubs for energy metric.

#ifndef RPL_CONF_OF
#define RPL_CONF_OF rpl_mrhof
#endif

The Expected Transmissions metric (ETX) measure how many tries it takes to receive an acknowledgment (ACK) of a sent packet, keeping a moving average for each neighbor, computing the sum of all ETXs to build the routes.

As default Contiki uses storing mode for RPL downward routes. Basically all nodes store in a routing table the addresses of their child nodes.

Set up a wireless sniffer

A packet sniffer is a must-have tool for any wireless network application, it allows to see what you are transmitting over the air, verifying both that the transmissions are taking place, the frames/packets are properly formatted, and that the communication is happening on a given channel.

There are commercial options available, such as the Texas Instruments SmartRF packet Sniffer, which can be used with a CC2531 USB dongle to capture packets like the one below.

image006
Figure 9. Sniffer packet capture

We will use for this exercise the SenSniff application, paired with a RE-Mote and Wireshark (already installed in instant Contiki). This setup will allow us to understand how the wireless communication is done in Contiki.

To program the RE-Mote or a Z1 as a packet Sniffer:

cd examples/zolertia/tutorial/02-ipv6/06-sniffer

Compile and program:

make TARGET=zoul sniffer.upload

Or

make TARGET=z1 sniffer.upload

Note this application has its own project-conf.h, not shared with the other applications in the same 02-ipv6 folder.

The project-conf.h has specific platform settings to make the sniffer work (don’t change those unless you know what you are doing), only change the following:

#undef IEEE802154_CONF_PANID
#define IEEE802154_CONF_PANID      0xABCD

#if CONTIKI_TARGET_ZOUL
/* The following are Zoul (RE-Mote, etc) specific */
#undef CC2538_RF_CONF_CHANNEL
#define CC2538_RF_CONF_CHANNEL     26

#else /* Default is Z1 */

/* The following are Z1 specific */
#undef RF_CHANNEL
#define RF_CHANNEL                 26

#undef CC2420_CONF_CHANNEL
#define CC2420_CONF_CHANNEL        26

For the Z1 mote launch the sensniff application with the following command:

python sensniff.py --non-interactive -d /dev/ttyUSB0 -b 115200

For the RE-Mote:

python sensniff.py --non-interactive -d /dev/ttyUSB0 -b 460800

Sensniff will read data from the mote over the serial port, dissect the frames and pipe to /tmp/sensniff by default, now we need to connect the other extreme of the pipe to wireshark, else you will get the following warning:

 "Remote end not reading"

Which is not worrisome, it only means that the other pipe endpoint is not connected. You can also save the sniffed frames for later opening with wireshark, adding the following argument to the above command -p name.pcap, which will save the session output in a name.pcap file. Change the naming and location for storing the file accordingly.

Note

At the moment of writing this tutorial changing channels from the Sensniff application was not implemented but proposed as a feature.

Open another terminal and launch wireshark with the following command, which will add the pipe as a capture interface:

sudo wireshark -i /tmp/sensniff

Select the /tmp/sensniff interface from the droplist and click Start just above.

image007
Figure 10. Capture options

Make sure that the pipe is configured to capture packets in promiscuous mode, if needed you can increase the buffer size, but 1 MB is normally enough.

image008
Figure 11. Interface settings

Now the captured frames should start to appear on screen.

image009
Figure 12. Captured frames

You can add specific filters to limit the frames being shown on screen, for this example click at the Expression button and a list of available attributes per protocol are listed, scroll down to IEEE 802.15.4 and check the available filters. You can also chain different filter arguments using the Filter box, in this case we only wanted to check the frames belonging to the PAN 0xABCD and coming from node c1:0c::0309, so we used the wpan.dst_pan and wpan.src64 attributes.

image010
Figure 13. Wireshark filters

When closing the Sensniff python application, a session information is provided reporting the following statistics:

Frame Stats:
         Non-Frame: 6
         Not Piped: 377
    Dumped to PCAP: 8086
             Piped: 7709
          Captured: 8086

Now that we have a sniffer configured and ready to use, let’s create a first wireless application and start sniffing packets!

UDP on IPv6 and the Border Router

Now that we have covered the mote configurations and the MAC and routing layers, let us set up a UDP network.

What is UDP?

UDP (User Datagram Protocol) is a communications protocol that offers a limited amount of services for messages exchanged among devices in a network that uses the Internet Protocol (IP).

UDP is an alternative to the Transmission Control Protocol (TCP) and, together with IP, is sometimes referred to as UDP/IP. Like the Transmission Control Protocol, UDP uses the Internet Protocol to actually get a data unit (called a datagram) from one computer to another.

Unlike TCP, UDP does not provide message fragmentation and reassembling at the other end, this means that the application must be able to make sure that the entire message has arrived and is in the right order.

Network applications that want to save processing time because they have very small data units to exchange (and therefore very little message reassembling to do) may prefer UDP to TCP

The UDP implementation is Contiki resides in core/net/ip. The remainder of the section will focus on describing the UDP available functions.

The UDP API

We need to create a socket for the connection, this is done using the udp_socket structure, which has the following elements:

struct udp_socket {
  udp_socket_input_callback_t input_callback;
  void *ptr;
  struct process *p;
  struct uip_udp_conn *udp_conn;
};

After creating the UDP socket structure, we need to register the UDP socket. This is done with the udp_socket_register.

/**
 * \brief      Register a UDP socket
 * \param c    A pointer to the struct udp_socket that should be registered
 * \param ptr  An opaque pointer that will be passed to callbacks
 * \param receive_callback A function pointer to the callback function that will be called when data arrives
 * \retval -1  The registration failed
 * \retval 1   The registration succeeded
 */
int udp_socket_register(struct udp_socket *c,
                        void *ptr,
                        udp_socket_input_callback_t receive_callback);

As the UDP socket has been created and registered, let us listen on a given port. The udp_socket_bind function binds the UDP socket to a local port so it will begin to receive data that arrives on the specified port. A UDP socket will receive data addressed to the specified port number on any IP address of the host. A UDP socket bound to a local port will use this port number as source port for outgoing UDP messages.

/*
 * \brief      Bind a UDP socket to a local port
 * \param c    A pointer to the struct udp_socket that should be bound to a local port
 * \param local_port The UDP port number, in host byte order, to bind the UDP socket to
 * \retval -1  Binding the UDP socket to the local port failed
 * \retval 1   Binding the UDP socket to the local port succeeded
 */
int udp_socket_bind(struct udp_socket *c,
                    uint16_t local_port);

The udp_socket_connect function connects the UDP socket to a specific remote port and optional remote IP address. When a UDP socket is connected to a remote port and address, it will only receive packets that are sent from that remote port and address. When sending data over a connected UDP socket, the data will be sent to the connected remote address.

A UDP socket can be connected to a remote port, but not to a remote IP address, by providing a NULL parameter as the remote_addr parameter. This lets the UDP socket receive data from any IP address on the specified port.

/**
 * \brief      Bind a UDP socket to a remote address and port
 * \param c    A pointer to the struct udp_socket that should be connected
 * \param remote_addr The IP address of the remote host, or NULL if the UDP socket should only be connected to a specific port
 * \param remote_port The UDP port number, in host byte order, to which the UDP socket should be connected
 * \retval -1  Connecting the UDP socket failed
 * \retval 1   Connecting the UDP socket succeeded
 */
int udp_socket_connect(struct udp_socket *c,
                       uip_ipaddr_t *remote_addr,
                       uint16_t remote_port);

To send data over a connected UDP socket it must have been connected to a remote address and port with udp_socket_connect.

/**
 * \brief      Send data on a UDP socket
 * \param c    A pointer to the struct udp_socket on which the data should be sent
 * \param data A pointer to the data that should be sent
 * \param datalen The length of the data to be sent
 * \return     The number of bytes sent, or -1 if an error occurred
 */
int udp_socket_send(struct udp_socket *c,
                    const void *data, uint16_t datalen);

To send data over a UDP socket without being connected we use the function udp_socket_sendto instead.

/**
 * \brief      Send data on a UDP socket to a specific address and port
 * \param c    A pointer to the struct udp_socket on which the data should be sent
 * \param data A pointer to the data that should be sent
 * \param datalen The length of the data to be sent
 * \param addr The IP address to which the data should be sent
 * \param port The UDP port number, in host byte order, to which the data should be sent
 * \return     The number of bytes sent, or -1 if an error occurred
 */
int udp_socket_sendto(struct udp_socket *c,
                      const void *data, uint16_t datalen,
                      const uip_ipaddr_t *addr, uint16_t port);

To close a UDP socket previously registered with udp_socket_register the function below is used. All registered UDP sockets must be closed before exiting the process that registered them, or undefined behavior may occur.

/**
 * \brief      Close a UDP socket
 * \param c    A pointer to the struct udp_socket to be closed
 * \retval -1  If closing the UDP socket failed
 * \retval 1   If closing the UDP socket succeeded
 */
int udp_socket_close(struct udp_socket *c);

Each UDP socket has a callback function that is registered as part of the call to udp_socket_register. The callback function gets called every time a UDP packet is received.

/**
 * \brief      A UDP socket callback function
 * \param c    A pointer to the struct udp_socket that received the data
 * \param ptr  An opaque pointer that was specified when the UDP socket was registered with udp_socket_register()
 * \param source_addr The IP address from which the datagram was sent
 * \param source_port The UDP port number, in host byte order, from which the datagram was sent
 * \param dest_addr The IP address that this datagram was sent to
 * \param dest_port The UDP port number, in host byte order, that the datagram was sent to
 * \param data A pointer to the data contents of the UDP datagram
 * \param datalen The length of the data being pointed to by the data pointer
 */
typedef void (* udp_socket_input_callback_t)(struct udp_socket *c,
                                             void *ptr,
                                             const uip_ipaddr_t *source_addr,
                                             uint16_t source_port,
                                             const uip_ipaddr_t *dest_addr,
                                             uint16_t dest_port,
                                             const uint8_t *data,
                                             uint16_t datalen);

Alternatively there is another UDP library called simple-udp, which simplifies the UDP API to fewer functions. The library is located in core/net/ip/simple-udp.c. For the next example we are going to use the simple-udp library, to show how to create a very first wireless example. In a later example we will come back to the full-fledged UDP API.

This is the first example of the 02-ipv6 folder.

The 01-udp-local-multicast summarizes how to read and set radio parameters, such as:

  • RSSI (Received signal strength indication) and LQI (Link quality indicator)

  • Radio channel

  • PAN ID (network identifier)

We will also learn how to use the Simple-UDP library, which allows to create wireless applications on top of IPv6/UDP, and transmit data such as sensor readings and system information.

You will need at least two Zolertia motes, the RE-Mote and Z1 can be used together in the same network as the network implementation is platform-independent.

Flash the two Zolertia devices as:

make 01-udp-local-multicast.upload
Tip

Remember to use the PORT argument to specify the USB port in which a given Zolertia platform is connected. If you have a RE-Mote connected in /dev/ttyUSB0 port and another one in /dev/ttyUSB1 but you only want to flash the first one, then:

make TARGET=zoul 01-udp-local-multicast.upload PORT=/dev/ttyUSB1

Likewise to open a serial console over USB to see the debug output:

make TARGET=zoul login PORT=/dev/ttyUSB0

The RE-Mote platform will show something similar to:

Contiki-3.x-2576-g4499d80
Zolertia RE-Mote platform
Rime configured with address 00:12:4b:00:06:15:ab:25  (1)
 Net: sicslowpan                                      (2)
 MAC: CSMA                                            (3)
 RDC: nullrdc                                         (4)

* Radio parameters:
   Channel 15 (Min: 11, Max: 26)                      (5)
   Tx Power   3 dBm (Min: -24 dBm, Max:   7 dBm)      (6)
   PAN ID: 0xBEEF

***
Sending packet to multicast adddress ff02::1          (7)
ID: 171, core temp: 37.619, ADC1: 2332, ADC2: 0, ADC3: 1496, batt: 3300, counter: 1
  1. MAC address of the RE-Mote

  2. 6LoWPAN implementation in Contiki

  3. Carrier Sense Multiple Access Medium Access Control CSMA

  4. Radio Duty cycling disabled (NullRDC)

  5. 2.4GHz RF channel (11-26 available)

  6. Transmission power (-24dBm to 7dBm)

As probably noticed, the default values for the 2.4GHz band channel is 26, not 15…​ why is now different? let’s take a closer look at the code:

PROCESS_THREAD(mcast_example_process, ev, data)
{
  static struct etimer periodic_timer;

  /* Data container used to store the IPv6 addresses */
  uip_ipaddr_t addr;

  PROCESS_BEGIN();

  set_radio_default_parameters();
  print_radio_values();

  (...)
}

The set_radio_default_parameters() configures the radio values to be used by the application:

static void
set_radio_default_parameters(void)
{
  NETSTACK_RADIO.set_value(RADIO_PARAM_TXPOWER, EXAMPLE_TX_POWER);
  NETSTACK_RADIO.set_value(RADIO_PARAM_PAN_ID, EXAMPLE_PANID);
  NETSTACK_RADIO.set_value(RADIO_PARAM_CHANNEL, EXAMPLE_CHANNEL);
}

But where are the EXAMPLE_CHANNEL, EXAMPLE_TX_POWER and EXAMPLE_PANID values defined?

Tip

Try to do the following before reading further: grep -lr "EXAMPLE_CHANNEL" /home/user/contiki

The Makefile shows the location of the project-conf.h configuration file:

# Includes the project-conf configuration file
CFLAGS += -DPROJECT_CONF_H=\"../project-conf.h\"

# This flag includes the IPv6 libraries
CONTIKI_WITH_IPV6 = 1

Notice the CONTIKI_WITH_IPV6 flag, this effectively enabled IPv6 support in our application.

The project-conf-h file is in 02-ipv6, and configures the following relevant values:

/* Comment this out to use Radio Duty Cycle (RDC) and save energy */
#undef NETSTACK_CONF_RDC
#define NETSTACK_CONF_RDC          nullrdc_driver

#undef IEEE802154_CONF_PANID
#define IEEE802154_CONF_PANID      0xABCD

/* The following are Z1 specific */
#undef RF_CHANNEL
#define RF_CHANNEL	               26

#undef CC2420_CONF_CHANNEL
#define CC2420_CONF_CHANNEL        26

/* The following are Zoul (RE-Mote, etc) specific */
#undef CC2538_RF_CONF_CHANNEL
#define CC2538_RF_CONF_CHANNEL     26

Now we know we are disabling Radio-Duty cycling and using NullRDC and keeping the radio on, but the channel value default value is 26, not 15.

In the same 02-ipv6 folder there is a header file named example.h which defines:

/* This is the UDP port used to send and receive data */
#define UDP_CLIENT_PORT   8765       (1)
#define UDP_SERVER_PORT   5678       (2)

/* Radio values to be configured for the 01-udp-local-multicast example */
#define EXAMPLE_TX_POWER  31         (3)
#define EXAMPLE_CHANNEL   15         (4)
#define EXAMPLE_PANID     0xBEEF     (5)

/* This data structure is used to store the packet content (payload) */
struct my_msg_t {                    (6)
  uint8_t  id;
  uint16_t counter;
  uint16_t value1;
  uint16_t value2;
  uint16_t value3;
  uint16_t value4;
  uint16_t battery;
};
  1. The UDP port opened by the device itself

  2. The UDP port to send data to (opened by the server)

  3. The new transmission power set in the example

  4. The new channel set in the example

  5. The new PAN ID configured in the example

  6. The structure in which we are to store the data to be sent wirelessly

Let’s go back to the 01-udp-local-multicast.c example:

/* The structure used in the Simple UDP library to create an UDP connection */
static struct simple_udp_connection mcast_connection;

/* Create the UDP connection.  This function registers a UDP connection and
 * attaches a callback function to it. The callback function will be
 * called for incoming packets. The local UDP port can be set to 0 to indicate  * that an ephemeral UDP port should be allocated. The remote IP address can
 * be NULL, to indicate that packets from any IP address should be accepted.
 */
simple_udp_register(&mcast_connection, UDP_CLIENT_PORT, NULL, UDP_CLIENT_PORT, receiver);

First we have configured an UDP connection, passing as an argument the mcast_connection structure to be used by the simple-udp library. As the remote IP address is set to NULL any incoming packet will be accepted, invoking the receiver callback handler whenever that happens:

static void
receiver(struct simple_udp_connection *c,
         const uip_ipaddr_t *sender_addr,
         uint16_t sender_port,
         const uip_ipaddr_t *receiver_addr,
         uint16_t receiver_port,
         const uint8_t *data,
         uint16_t datalen)
{
  radio_value_t aux;
  struct my_msg_t *msgPtr = (struct my_msg_t *) data;   (1)
  leds_toggle(LEDS_GREEN);
  uip_debug_ipaddr_print(sender_addr);                  (2)

  printf("\nData received on port %d from port %d with  (3)
         length %d\n", receiver_port, sender_port, datalen);

  NETSTACK_RADIO.get_value(RADIO_PARAM_CHANNEL, &aux);  (4)
  printf("CH: %u ", (unsigned int) aux);

  aux = packetbuf_attr(PACKETBUF_ATTR_RSSI);            (5)
  printf("RSSI: %ddBm ", aux);

  aux = packetbuf_attr(PACKETBUF_ATTR_LINK_QUALITY);    (6)
  printf("LQI: %u\n", aux);

  /* Print the received data */                         (7)
  printf("ID: %u, core temp: %u, ADC1: %d, ADC2: %d, ADC3: %d",
          msgPtr->id, msgPtr->value1, msgPtr->value2, msgPtr->value3,
          msgPtr->value4);
  printf("batt: %u, counter: %u\n", msgPtr->battery,
          msgPtr->counter);
}
  1. Creates a pointer to dereference the message payload into the my_msg_t structure defined in example.h

  2. A helper function used to print an IPv6 address in a readable form

  3. Prints the UDP origin and destination port, and the message length

  4. Retrieve the current RF channel number

  5. Retrieve the RSSI level of the received message (on a 1-hop basis)

  6. Retrieve the LQI value of the received message

  7. Prints the payload content

As probably you have already noticed, we are sending the on-board sensor readings via wireless to the all-nodes multicast address:

  etimer_set(&periodic_timer, SEND_INTERVAL);

  while(1) {
    PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&periodic_timer));
    /* Create a link-local multicast address to all nodes */
    uip_create_linklocal_allnodes_mcast(&addr);
    uip_debug_ipaddr_print(&addr);
    printf("\n");

    /* Take sensor readings and store into the message structure */
    take_readings();

    /* Send the multicast packet to all devices */
    simple_udp_sendto(&mcast_connection, msgPtr, sizeof(msg), &addr);

    etimer_reset(&periodic_timer);
  }

The take_readings() function reads the sensors values and then stores it into msg, a my_msg_t structure.

/* Create a structure and pointer to store the data to be sent as payload */
static struct my_msg_t msg;
static struct my_msg_t *msgPtr = &msg;

Notice the simple_udp_sendto accepts any type of data as payload, we use the msgPtr pointer which points to msg and the sizeof(msg) function to inform simple-udp how large is our payload.

Program at least two Z1 or RE-Mote with the 01-udp-local-multicast.c application, and open a console connection using make login for each connected device.

You should be receiving messages from the other devices! Write down the node ID of other motes. This will be useful later.

Tip

Experiment with the channel, transmission power, RSSI and LQI levels:

  • How does the RSSI/LQI change when moving the devices apart?

  • What happens if you put an obstacle between both devices? a metallic obstacle would yield the same effect as a wooden one?

  • Rotate the RE-Mote or the Z1 so the antenna position is not the same in all cases, what do you think it will happen?

  • Decrease the transmission power close to the minimum and try to move the devices away from each other until you cannot receive any more packets, note the distance and the RSSI/LQI values.

  • What is the maximum range you get using the maximum transmission power between two devices with line of sight? what will happen if you increase the height of one of the devices by a metre?

To change the sending interval you can also modify the values at:

#define SEND_INTERVAL	(15 * CLOCK_SECOND)
Tip

Program at least one Z1 or RE-Mote with the 01-udp-local-multicast example and another device with the sniffer application and sniff the traffic! try to filter outgoing and incoming data packets using your own rules.

Remember you are using the 01-udp-local-multicast application with the channel 15 and PAN ID 0xBEEF, you must change your sniffer settings also in its project-conf.h file.

The Border Router

The border router or edge router is typically a device sitting at the edge of our network, which allow us to talk to outside networks using its built-in network interfaces, such as WiFi, Ethernet, Serial, etc.

image011
Figure 14. The border router

In Contiki the current and most used border router application implements a serial-based interface called SLIP, it allows to connect a given mote to a host using scripts like tunslip6 in tools/tunslip6 over the serial port, creating a tunnelled network interface, which can be given an IPv6 prefix to set the network global IPv6 addresses.

A simplification of tunslip6 and the Border Router is: IPv6 packets received by the host are forwarded over the USB connection to the Border Router via tunslip6, and subsequently all 6LoWPAN wireless packets are forwarded by the Border Router to the host as IPv6 packets via the USB connection through tunslip6.

The border router application is located at examples/ipv6/rpl-border-router, the following code snippets are the most relevant:

/* Request prefix until it has been received */
while(!prefix_set) {
   etimer_set(&et, CLOCK_SECOND);
   request_prefix();
   PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));
}

dag = rpl_set_root(RPL_DEFAULT_INSTANCE,(uip_ip6addr_t *)dag_id);
if(dag != NULL) {
   rpl_set_prefix(dag, &prefix, 64);
   PRINTF("created a new RPL dag\n");
}

The snippet above bootstraps until a valid prefix has been given. Once the prefix has been assigned, the node will set the prefix and convert itself in the root node (DODAG).

Normally it is preferable to configure the border router as a non-sleeping device, so that the radio receiver is always on (no radio duty cycling).

By default the border-router applications includes a built-in web server, displaying information about the network, such as the immediate neighbors (1-hop away) and the known routes to nodes in their networks. To enable the web server, the WITH_WEBSERVER flag should be enabled, and by default it will add the httpd-simple.c application.

The following assumes to use a RE-Mote platform, but the Z1 mote can be used as well.

make TARGET=zoul savetarget

To compile, flash the mote and connect the border router to your host; run:

cd /home/user/contiki/examples/zolertia/tutorial/02-ipv6/02-border-router
make border-router.upload && make connect-router

By default it will try to connect to a mote at port /dev/ttyUSB0 using the following serial settings: 115200 baud rate, 8 bits, No parity and 1 bit stop. If you do not state an IPv6 prefix it will use the default aaaa::1/64, to specify a different one run the tunslip tool instead using the following:

make connect-router PREFIX="2001:abcd:dead:beef::1/64"

Or if you want to specify a different USB port:

make connect-router PREFIX="-s /dev/ttyUSB1 fd00::1/64"

You can also compile and run the tunslip6 tool directly from the tools location, to compile just type:

cd tools
cc tunslip6.c -o tunslip6

And to run with specific arguments, i.e. connect to a specific serial port, name your tunnel connection with a specific name, or proxify to a given address and port, use the following:

./tunslip -s /dev/ttyUSB0 -t tun0 2001:abcd:dead:beef::1/64

Run tunslip -H for more information.

Important

To enable IPv6 forwarding you need to run:

sudo sysctl -w net.ipv6.conf.all.forwarding=1

To make this change permanent, edit the /etc/sysctl.conf file and uncomment the following line:

net.ipv6.conf.default.forwarding=1

The output of the tunslip6 script is:

using saved target 'z1'
sudo ../../../../../tools/tunslip6 fd00::1/64
[sudo] password for zolertia:
********SLIP started on ``/dev/ttyUSB0''
opened tun device ``/dev/tun0''
ifconfig tun0 inet `hostname` mtu 1500 up
ifconfig tun0 add fd00::1/64
ifconfig tun0 add fe80::0:0:0:1/64
ifconfig tun0

tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          inet addr:127.0.1.1  P-t-P:127.0.1.1  Mask:255.255.255.255
          inet6 addr: fe80::1/64 Scope:Link
          inet6 addr: fd00::1/64 Scope:Global
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:500
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

*** Address:fd00::1 => fd00:0000:0000:0000
Got configuration message of type P
Setting prefix fd00::
Server IPv6 addresses:
 fd00::c30c:0:0:13c2
 fe80::c30c:0:0:13c2

This will create a tun0 interface in our host, try running the ifconfig command in a terminal.

Note the Border Router uses Stateless Auto Configuration SLAC to create its IPv6 address, by using the IPv6 prefix given to the tunslip6 script and its own MAC address.

Note

Contiki has changed the default prefix from aaaa::/64 to fd00::/64 recently to the date of this book, so there might be example images and documentation still using the old prefix. You can use any of them, but it is preferred the later. Add the following to your project-conf.h file and change the value accordingly to be consistent to the chosen prefix:

#define UIP_CONF_DS6_DEFAULT_PREFIX 0xaaaa

We can make a ping6 request to both the tun0 interface and to the Border Router:

$ ping6 fd00::c30c:0:0:13c2
PING fd00::c30c:0:0:13c2(fd00::c30c:0:0:13c2) 56 data bytes
64 bytes from fd00::c30c:0:0:13c2: icmp_seq=1 ttl=64 time=19.8 ms
64 bytes from fd00::c30c:0:0:13c2: icmp_seq=2 ttl=64 time=20.3 ms
64 bytes from fd00::c30c:0:0:13c2: icmp_seq=3 ttl=64 time=20.5 ms
64 bytes from fd00::c30c:0:0:13c2: icmp_seq=4 ttl=64 time=20.8 ms
64 bytes from fd00::c30c:0:0:13c2: icmp_seq=5 ttl=64 time=20.6 ms
64 bytes from fd00::c30c:0:0:13c2: icmp_seq=6 ttl=64 time=20.6 ms

Furthermore, we can open a web browser and enter the Border Router’s IPv6 address, in this example:

http://[fd00::c30c:0:0:13c2]
image016
Figure 15. Border Router web service

The built-in web service will show the Border Router’s neighbors list and its routes. This is quite handy to check if the devices in our deployment have joined our Border Router’s network, retrieve their IPv6 addresses and even make a ping6 request.

Note we have been used a local IPv6 prefix, routable only in our network for testing purposes. It is also possible to assign the tunslip6 a globally routable IPv6 prefix, thus connecting our Border Router to Internet.

image017
Figure 16. Border Router web service with IPv6 global address

Using a public and globally reachable IPv6 prefix will make our Border Router, and subsequently all devices inside our IPv6/6LoWPAN network accessible over Internet:

image018
Figure 17. IPv6 online ping6

This means our 6LoWPAN network of constrained devices is reachable from anywhere in the world, and viceversa.

Tip

6lbr is a deployment-ready 6LoWPAN border router solution based on Contiki, it has support for the Z1 mote and the RE-Mote platform. To take your border router to the next level, this is the tool you have been looking for.

If you launch the sniffer you will be able to see how the Border Router starts sending the RPL control messages, such as DIO (DAG Information Object) messages. If you flash another device with the previously used 01-udp-multicast.c example, you would be able to also see how a device joins the RPL network by exchanging DIO, DAO (Destination Advertisement Object) and DIS (DAG Information Solicitation) messages.

Tip

Remember the 01-udp-multicast and the 02-border-router examples uses a different channel and PAN ID! don’t forget to change accordingly: probably the best place is to edit the example.h header file.

image025
Figure 18. Wireshark capture of a node joining a RPL network

Hands on: connecting an IPv6 UDP network to our host

In the 02-ipv6 folder open the 03-udp-client-and-server example.

The example shows how to send sensor information to an UDP server running on the host network, even on a different network over Internet.

The UDP server is implemented in a python script, it allows to forward data also to other services.

The UDP server will send data to the MQTT broker of the Eclipse IoT foundation (at iot.eclipse.org), or to IFTTT to easily configure different types of events using a HTTP message as trigger.

Depending on the device the UDP client runs (Z1 or RE-Mote), you need to adjust in the UDP-MQTT-server.py or UDP-IFTTT-server.py the following:

EXAMPLE_WITH_Z1 = 1

This example is the easiest way if you want to send data to a service or server outside your network, and you don’t have an IPv6 network on your own. The Border Router as seen before can use a local IPv6 prefix and then the UDP server running on the host can forward to anywhere and anyone.

This example requires at least two Zolertia devices: A Border Router and an UDP client. You can use alternatively Z1 and RE-Motes as both can interoperate with each other.

You will also need to install the following python library if using the MQTT forwarder:

pip install paho-mqtt

You will need at least two Zolertia motes, the RE-Mote and Z1 can be used together in the same network.

The application (on the networking perspective) will be similar to:

image019
Figure 19. UDP client and server network architecture

And from the application service perspective the MQTT forwarder application will look like:

image020
Figure 20. UDP client and server MQTT application

And for the IFTTT example, we can implement a simple use case related to preventive maintenance: if the battery is close to deplete, or the device has been tampered (sensed by the accelerometer), or the radio link is close to fade (due to an unexpected obstacle), then schedule a calendar task to the maintenance crew.

image021
Figure 21. UDP client and server IFTTT application

Let’s start!

In the 03-udp-client.c example first note the server address:

/* Set the server address here */
uip_ip6addr(&server_ipaddr, 0xfd00, 0, 0, 0, 0, 0, 0, 1);

As we are assuming we are going to use the fd00::1/64 IPv6 address for the tunslip6 virtual interface, this will be the address of the host running the UDP-MQTT-server.py python script, if not change accordingly.

To verify that we have set the address correctly:

printf("Server address: ");
PRINT6ADDR(&server_ipaddr);
printf("\n");

The print_local_addresses(…​) function will print the device configured addresses:

static void
print_local_addresses(void)
{
  int i;
  uint8_t state;

  PRINTF("Client IPv6 addresses:\n");
  for(i = 0; i < UIP_DS6_ADDR_NB; i++) {
    state = uip_ds6_if.addr_list[i].state;
    if(uip_ds6_if.addr_list[i].isused &&
       (state == ADDR_TENTATIVE || state == ADDR_PREFERRED)) {
      PRINT6ADDR(&uip_ds6_if.addr_list[i].ipaddr);
      PRINTF("\n");
      /* hack to make address "final" */
      if (state == ADDR_TENTATIVE) {
        uip_ds6_if.addr_list[i].state = ADDR_PREFERRED;
      }
    }
  }
}

Printing at boot something similar to:

Server address: fd00::1
Client IPv6 addresses:
fe80::c30c:0:0:13c8

Remember addresses starting with fe80 are link-local, this means the device has not joined the RPL network, as it has not received an IPv6 prefix from the Border Router (the DODAG) to create its own IPv6 global address using SLAC.

As we know the prefix given to the Border Router is fd00::/64, we know the device in this example will have the fd00::c30c:0:0:13c8 IPv6 global address.

You can verify this later by checking the Border Router’s web service as done in the previous section.

The UDP connection is created in the following block:

  /* Create a new connection with remote host.  When a connection is created
   * with udp_new(), it gets a local port number assigned automatically.
   * The "UIP_HTONS()" macro converts to network byte order.
   * The IP address of the remote host and the pointer to the data are not used
   * so those are set to NULL
   */
  client_conn = udp_new(NULL, UIP_HTONS(UDP_SERVER_PORT), NULL);

  if(client_conn == NULL) {
    PRINTF("No UDP connection available, exiting the process!\n");
    PROCESS_EXIT();
  }

  /* This function binds a UDP connection to a specified local por */
  udp_bind(client_conn, UIP_HTONS(UDP_CLIENT_PORT));

  PRINTF("Created a connection with the server ");
  PRINT6ADDR(&client_conn->ripaddr);
  PRINTF(" local/remote port %u/%u\n", UIP_HTONS(client_conn->lport),
                                       UIP_HTONS(client_conn->rport));

As you have probably noticed, we are using the same project-conf.h and the example.h headers from the previous 01-udp-multicast.c example. In this case we are using both UDP client and server port, opening the first to receive data from the UDP server running in the python script, and the second value to send data to the UDP server.

Upon receiving a message (from the UDP server for example) the tcpip_handler is called to process the incoming data:

static void
tcpip_handler(void)
{
  char *str;

  if(uip_newdata()) {
    str = uip_appdata;
    str[uip_datalen()] = '\0';
    printf("Received from the server: '%s'\n", str);
  }
}

And as done in the previous example the devices will send data from its on-board sensors using the my_msg_t structure to store the data as payload.

  while(1) {
    PROCESS_YIELD();

    /* Incoming events from the TCP/IP module */
    if(ev == tcpip_event) {
      tcpip_handler();
    }

    /* Send data to the server */
    if((ev == sensors_event && data == &button_sensor) ||
       (ev == PROCESS_EVENT_TIMER)) {

      send_packet();

      if(etimer_expired(&periodic)) {
        etimer_reset(&periodic);
      }
    }
  }

The sensors information is sent periodically as configured by the SEND_INTERVAL value (default is every minute, change as you wish), or by pressing the user button to request an immediate packet to be sent to the UDP server.

The code snippet related to the payload sent to the UDP server address is shown next:

PRINTF("Send readings to %u'\n",
        server_ipaddr.u8[sizeof(server_ipaddr.u8) - 1]);

uip_udp_packet_sendto(client_conn, msgPtr, sizeof(msg),
                        &server_ipaddr, UIP_HTONS(UDP_SERVER_PORT));

Compile and program the mote:

make TARGET=zoul savetarget
make 03-udp-client.upload && make login

The output should be something similar to:

Contiki-3.x-2611-g61576c3
Zolertia RE-Mote platform
CC2538: ID: 0xb964, rev.: PG2.0, Flash: 512 KiB, SRAM: 32 KiB, AES/SHA: 1, ECC/RSA: 1
System clock: 16000000 Hz
I/O clock: 16000000 Hz
Reset cause: External reset
Rime configured with address 00:12:4b:00:06:15:ab:25
 Net: sicslowpan
 MAC: CSMA
 RDC: nullrdc
UDP client process started
Server address: fd00::1
Client IPv6 addresses:
fe80::212:4b00:615:ab25
Created a connection with the server :: local/remote port 8765/5678

ID: 171, core temp: 34.47, ADC1: 2332, ADC2: 0, ADC3: 1500, batt: 3300, counter: 1
Send readings to 1'

Remember that you can also compile for the Z1 platform.

Remember to check the network and connection by using ping6, like you normally would test a wireless device. Open the Border Router’s web service and see if you can find the node in your neighbor list!

Once you found the node’s global IPv6 assigned address, make a ping6 request and check the ICMPv6 response. Don’t forget to use the wireless Sniffer and see how these ICMP packets looks like:

image026
Figure 22. ICMPv6 ping messages
Tip

With the Sniffer you are able to see the wireless messages within the 6LoWPAN network, but you can also capture in Wireshark the network traffic through the tunslip6 virtual interface (normally tun depending on the parameters you have used to create it).

There is a Wireshark capture saved as an example for you to take a look in wireshark-ipv6-6lowpan.pcap.

Running the UDP server MQTT forwarder

To execute the UDP-MQTT-server.py script just run:

python UDP-MQTT-server.py

This is the expected output when running and receiving a UDP packet from a Z1 node:

UDP6 server side application V0.1
Started 2016-02-26 09:23:49.673940
UDP server ready: 5678
msg structure size:  13

MQTT: Connected (0)
2016-02-26 09:23:58 -> fd00::c30c:0:0:13c8:8765 14
{
  "values": [
    {
      "value": 171,
      "key": "id"
    },
    {
      "value": 0,
      "key": "counter"
    },
    {
      "value": 2320,
      "key": "temperature"
    },
    {
      "value": -135,
      "key": "x_axis"
    },
    {
      "value": 40,
      "key": "y_axis"
    },
    {
      "value": -120,
      "key": "z_axis"
    },
    {
      "value": 2981,
      "key": "battery"
    }
  ]
}
MQTT: Publishing to {0}... 0 (171)
Sending reply to fd00::c30c:0:0:13c8
MQTT: Published 2

If we run the mqtt-client.py python script in a separate terminal we will receive a notification each time the UDP server publishes new data to the v2/zolertia/tutorialthings/# topic:

python mqtt-client.py
connecting to iot.eclipse.org
Connected with result code 0
Subscribed to v2/zolertia/tutorialthings/#
v2/zolertia/tutorialthings/171 {"values":[{"key": "id", "value": 171},{"key": "counter", "value": 0},{"key": "temperature", "value": 2326},{"key": "x_axis", "value": -129},{"key": "y_axis", "value": 38},{"key": "z_axis", "value": -122},{"key": "battery", "value": 2987}]}
Note

The udp-client devices publish to the following MQTT topic:

v2/zolertia/tutorialthings/{{ID}}

Where {{ID}} corresponds to:

msg.id = 0xAB;

Thus the default topic will be v2/zolertia/tutorialthings/171 as shown in the previous snippet. To change the topic’s {{ID}} just change the msg.id value in 03-udp-client.c.

The pound sign in v2/zolertia/tutorialthings/# is a wildcard value, it will subscribe to any topic starting with v2/zolertia/tutorialthings/.

If you want to change the default MQTT topic, change the following in UDP-MQTT-server.py:

MQTT_URL_PUB = "v2/zolertia/tutorialthings/"

If you have an android mobile phone you can download the MyMQTT application and receive the notifications in your phone:

image012
Figure 23. MyMQTT android app
Server : "iot.eclipse.org"
Port   : 1883
Username/password not required
Topic: v2/zolertia/tutorialthings/#
Running the UDP server IFTTT forwarder

Create an IFTTT account and subscribe to the Maker Channel:

image022
Figure 24. IFTTT Maker channel

And copy your Key as shown below:

image023
Figure 25. IFTTT Maker channel configuration values

Then create the Recipe you want, for example to automatically create a calendar entry to the maintenance crew if the battery level is critical or the temperature of the room is too high, send an email whenever someones pushes the button, etc…​ you have plenty of channels to choose!

image024
Figure 26. IFTTT example recipe

In the UDP-IFTTT-server.py just add the event and key values:

IFTTT_EVENT = "test"
IFTTT_KEY   = ""

TCP on IPv6

What is TCP?

The Transmission Control Protocol (TCP) is a core protocol of the Internet Protocol (IP).

TCP is a reliable stream delivery service that ensures that all bytes received will be in the correct order. It uses a technique known as positive acknowledgment with retransmission to guarantee reliability of packet transfer. TCP handles the received fragments and reorders the data.

Applications that do not require reliable data stream service may use the User Datagram Protocol (UDP), which provides a connectionless datagram service that emphasizes reduced latency over reliability.

TCP is commonly used by HTTP, FTP, email and any connection-oriented service.

The TCP implementation is Contiki resides in core/net/ip. The remainder of the section will focus on describing the TCP available functions.

The TCP API

We need to create a socket for the connection, this is done using the tcp_socket structure, which has the following elements:

struct tcp_socket {
  struct tcp_socket *next;

  tcp_socket_data_callback_t input_callback;
  tcp_socket_event_callback_t event_callback;
  void *ptr;

  struct process *p;

  uint8_t *input_data_ptr;
  uint8_t *output_data_ptr;

  uint16_t input_data_maxlen;
  uint16_t input_data_len;
  uint16_t output_data_maxlen;
  uint16_t output_data_len;
  uint16_t output_data_send_nxt;
  uint16_t output_senddata_len;
  uint16_t output_data_max_seg;

  uint8_t flags;
  uint16_t listen_port;
  struct uip_conn *c;
};

Socket status:

enum {
  TCP_SOCKET_FLAGS_NONE      = 0x00,
  TCP_SOCKET_FLAGS_LISTENING = 0x01,
  TCP_SOCKET_FLAGS_CLOSING   = 0x02,
};

After creating the TCP socket structure, we need to register the TCP socket. This is done with the tcp_socket_register, which takes as arguments the TCP socket, and input/output buffers to use for sending and receiving data. Be sure to dimension these buffers according to the expected amount of data to be sent and received.

/**
 * \brief      Register a TCP socket
 * \param s    A pointer to a TCP socket
 * \param ptr  A user-defined pointer that will be sent to callbacks for this socket
 * \param input_databuf A pointer to a memory area this socket will use for input data
 * \param input_databuf_len The size of the input data buffer
 * \param output_databuf A pointer to a memory area this socket will use for outgoing data
 * \param output_databuf_len The size of the output data buffer
 * \param data_callback A pointer to the data callback function for this socket
 * \param event_callback A pointer to the event callback function for this socket
 * \retval -1  If an error occurs
 * \retval 1   If the operation succeeds.
 */
int tcp_socket_register(struct tcp_socket *s, void *ptr,
                         uint8_t *input_databuf, int input_databuf_len,
                         uint8_t *output_databuf, int output_databuf_len,
                         tcp_socket_data_callback_t data_callback,
                         tcp_socket_event_callback_t event_callback);

As the TCP socket has been created and registered, let us listen on a given port. When a remote host connects to the port, the event callback will be called with the TCP_SOCKET_CONNECTED event. When the connection closes, the socket will go back to listening.

/**
 * \brief      Start listening on a specific port
 * \param s    A pointer to a TCP socket that must have been previously registered with tcp_socket_register()
 * \param port The TCP port number, in host byte order, of the remote host
 * \retval -1  If an error occurs
 * \retval 1   If the operation succeeds.
 */
int tcp_socket_listen(struct tcp_socket *s,
                      uint16_t port);

To stop listening on a given TCP port, just call the following function:

/**
 * \brief      Stop listening for new connections
 * \param s    A pointer to a TCP socket that must have been previously registered with tcp_socket_register()
 * \retval -1  If an error occurs
 * \retval 1   If the operation succeeds.
 */
int tcp_socket_unlisten(struct tcp_socket *s);

We can connect the TCP socket to a remote host. When the socket has connected, the event callback will get called with the TCP_SOCKET_CONNECTED event. If the remote host does not accept the connection, the TCP_SOCKET_ABORTED will be sent to the callback. If the connection times out before conecting to the remote host, the TCP_SOCKET_TIMEDOUT event is sent to the callback.

/**
 * \brief      Connect a TCP socket to a remote host
 * \param s    A pointer to a TCP socket that must have been previously registered with tcp_socket_register()
 * \param ipaddr The IP address of the remote host
 * \param port The TCP port number, in host byte order, of the remote host
 * \retval -1  If an error occurs
 * \retval 1   If the operation succeeds.
 */
int tcp_socket_connect(struct tcp_socket *s,
                       const uip_ipaddr_t *ipaddr,
                       uint16_t port);

As we are using an output buffer to send data over the TCP socket, a good practice is to query the TCP socket and check the number of bytes available.

/**
 * \brief      The maximum amount of data that could currently be sent
 * \param s    A pointer to a TCP socket
 * \return     The number of bytes available in the output buffer
 */
int tcp_socket_max_sendlen(struct tcp_socket *s);

To send data over a connected TCP socket the data is placed in the output buffer. When the data has been acknowledged by the remote host, the event callback is sent with the TCP_SOCKET_DATA_SENT event.

/**
 * \brief      Send data on a connected TCP socket
 * \param s    A pointer to a TCP socket that must have been previously registered with tcp_socket_register()
 * \param dataptr A pointer to the data to be sent
 * \param datalen The length of the data to be sent
 * \retval -1  If an error occurs
 * \return     The number of bytes that were successfully sent
 */
int tcp_socket_send(struct tcp_socket *s,
                    const uint8_t *dataptr,
                    int datalen);

Alternatively we can send a string over a TCP socket as follows:

/**
 * \brief      Send a string on a connected TCP socket
 * \param s    A pointer to a TCP socket that must have been previously registered with tcp_socket_register()
 * \param strptr A pointer to the string to be sent
 * \retval -1  If an error occurs
 * \return     The number of bytes that were successfully sent
 */
int tcp_socket_send_str(struct tcp_socket *s,
                        const char *strptr);

To close a connected TCP socket the function below is used. The event callback is called with the TCP_SOCKET_CLOSED event.

/**
 * \brief      Close a connected TCP socket
 * \param s    A pointer to a TCP socket that must have been previously registered with tcp_socket_register()
 * \retval -1  If an error occurs
 * \retval 1   If the operation succeeds.
 */
int tcp_socket_close(struct tcp_socket *s);

And to unregister a TCP socket the tpc_socket_unregister function is used. This function can also be used to reset a connected TCP socket.

/**
 * \brief      Unregister a registered socket
 * \param s    A pointer to a TCP socket that must have been previously registered with tcp_socket_register()
 * \retval -1  If an error occurs
 * \retval 1   If the operation succeeds.
 *
 *             This function unregisters a previously registered
 *             socket. This must be done if the process will be
 *             unloaded from memory. If the TCP socket is connected,
 *             the connection will be reset.
 *
 */
int tcp_socket_unregister(struct tcp_socket *s);

The TCP socket event callback function gets called whenever there is an event on a socket, such as the socket getting connected or closed.

/**
 * \brief      TCP event callback function
 * \param s    A pointer to a TCP socket
 * \param ptr  A user-defined pointer
 * \param event The event number
 */
typedef void (* tcp_socket_event_callback_t)(struct tcp_socket *s,
                                             void *ptr,
                                             tcp_socket_event_t event);

The TCP data callback function has to be added to the application, it will get called whenever there is new data on the socket:

/**
 * \brief      TCP data callback function
 * \param s    A pointer to a TCP socket
 * \param ptr  A user-defined pointer
 * \param input_data_ptr A pointer to the incoming data
 * \param input_data_len The length of the incoming data
 * \return     The function should return the number of bytes to leave in the input buffer
 */
typedef int (* tcp_socket_data_callback_t)(struct tcp_socket *s,
                                           void *ptr,
                                           const uint8_t *input_data_ptr,
                                           int input_data_len);

Hands on: TCP example

Now let us put to practice the TCP API described before and browse a TCP application. The 05-tpc-socket example simply echoes back the request done on port 80.

Then let us open the tcp-server.c example and browse the implementation.

The port 80 will be used for the TCP server to receive remote connections. As shown earlier we need to create a tcp_socket structure, and use two separate input/output buffers to send and receive data.

#define SERVER_PORT 80

static struct tcp_socket socket;

#define INPUTBUFSIZE 400
static uint8_t inputbuf[INPUTBUFSIZE];

#define OUTPUTBUFSIZE 400
static uint8_t outputbuf[OUTPUTBUFSIZE];

These two variables will be used to count the number of bytes received, and the bytes to be sent.

static uint8_t get_received;
static int bytes_to_send;

As commented earlier, we need to include a tcp_socket_event_callback_t to handle events.

static void
event(struct tcp_socket *s, void *ptr,
      tcp_socket_event_t ev)
{
  printf("event %d\n", ev);
}

We register the TCP socket and pass as a pointer the tcp_socket structure, the data buffers and our callback handlers. Next we start listening for connections on port 80.

tcp_socket_register(&socket, NULL,
               inputbuf, sizeof(inputbuf),
               outputbuf, sizeof(outputbuf),
               input, event);

tcp_socket_listen(&socket, SERVER_PORT);

The input callback handler receives the data, prints the string and its length, then if the received string is a complete request we save the number of bytes received into bytes_to_send (the atoi function converts string numbers into integers). If the received string is not complete, we return the number of bytes received to the driver to keep the data in the input buffer.

static int
input(struct tcp_socket *s, void *ptr,
      const uint8_t *inputptr, int inputdatalen)
{
  printf("input %d bytes '%s'\n", inputdatalen, inputptr);
  if(!get_received) {
    /* See if we have a full GET request in the buffer. */
    if(strncmp((char *)inputptr, "GET /", 5) == 0 &&
       atoi((char *)&inputptr[5]) != 0) {
      bytes_to_send = atoi((char *)&inputptr[5]);
      printf("bytes_to_send %d\n", bytes_to_send);
      return 0;
    }
    printf("inputptr '%.*s'\n", inputdatalen, inputptr);
    /* Return the number of data bytes we received, to keep them all
       in the buffer. */
    return inputdatalen;
  } else {
    /* Discard everything */
    return 0; /* all data consumed */
  }
}

The application will wait for an event to happen, in this case the incoming connection from above. After the event is handled, the code inside the while() loop and after the PROCESS_PAUSE() will be executed.

while(1) {
  PROCESS_PAUSE();

If we have previously received a complete request, we echo it back over the TCP socket. We use the tcp_socket_send_str function to send the header of the response as a string. The remainder of the data is sent until the bytes_to_send counter is empty.

    if(bytes_to_send > 0) {
      /* Send header */
      printf("sending header\n");
      tcp_socket_send_str(&socket, "HTTP/1.0 200 ok\r\nServer: Contiki tcp-socket example\r\n\r\n");

      /* Send data */
      printf("sending data\n");
      while(bytes_to_send > 0) {
        PROCESS_PAUSE();
        int len, tosend;
        tosend = MIN(bytes_to_send, sizeof(outputbuf));
        len = tcp_socket_send(&socket, (uint8_t *)"", tosend);
        bytes_to_send -= len;
      }
      tcp_socket_close(&socket);
    }
  }
  PROCESS_END();
}

When all the data is echoed back, the TCP socket is closed.