Hinweis: Bei dieser Seite handelt es sich um eine lokale Kopie der Contiki-Beispiele von Indria: http://senstools.gforge.inria.fr/doku.php?id=contiki:examples
This page describes different application examples for Contiki, detailing its main features, whether it's about processes or communication stacks.
In order to compile any Contiki application, the environment variable WSN430_DRIVERS_PATH
must point to the wsn430 drivers location.
The 01_hello-world example implements two concurrent processes. The first is called „hello_world“ and will periodically print „Hello World“ and an increasing counter on the serial port. The second is called „blink“ and will blink the LEDs at a higher rate than the other process.
The source files for this example is found in the example/01_hello-world
folder. The hello-world.c
file is the application main file, it contains all the application specific code and the Makefile
indicates how to build the application.
Here is the source code of the application:
#include "contiki.h" #include "dev/leds.h" #include <stdio.h> /* For printf() */ /*---------------------------------------------------------------------------*/ /* We declare the two processes */ PROCESS(hello_world_process, "Hello world process"); PROCESS(blink_process, "LED blink process"); /* We require the processes to be started automatically */ AUTOSTART_PROCESSES(&hello_world_process, &blink_process); /*---------------------------------------------------------------------------*/ /* Implementation of the first process */ PROCESS_THREAD(hello_world_process, ev, data) { // variables are declared static to ensure their values are kept // between kernel calls. static struct etimer timer; static int count = 0; // any process mustt start with this. PROCESS_BEGIN(); // set the etimer module to generate an event in one second. etimer_set(&timer, CLOCK_CONF_SECOND); while (1) { // wait here for an event to happen PROCESS_WAIT_EVENT(); // if the event is the timer event as expected... if(ev == PROCESS_EVENT_TIMER) { // do the process work printf("Hello, world #%i\n", count); count ++; // reset the timer so it will generate an other event // the exact same time after it expired (periodicity guaranteed) etimer_reset(&timer); } // and loop } // any process must end with this, even if it is never reached. PROCESS_END(); } /*---------------------------------------------------------------------------*/ /* Implementation of the second process */ PROCESS_THREAD(blink_process, ev, data) { static struct etimer timer; static uint8_t leds_state = 0; PROCESS_BEGIN(); while (1) { // we set the timer from here every time etimer_set(&timer, CLOCK_CONF_SECOND / 4); // and wait until the vent we receive is the one we're waiting for PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER); // update the LEDs leds_off(0xFF); leds_on(leds_state); leds_state += 1; } PROCESS_END(); } /*---------------------------------------------------------------------------*/
The code is rather fully documented, but let's see once more how processes are made:
PROCESS(name, strname)
. The arguments name
and strname
are the variable name and the process described name respectively. You will refer to the process using the process name
.PROCESS_THREAD(name, ev, data)
. The name
argument should be replaced by the process name
specified in the PROCESS
macro. ev
and data
should be let as they are, those will be variables that will indicate what event generated the process execution (ev
) and a pointer to optional data passed as the event was generated (data
).PROCESS_BEGIN()
and end with PROCESS_END()
. These two macro work together, as they in-fact implement a switch-statement. The code placed before PROCESS_BEGIN()
will be executed every time the process is executed (or scheduled), which is most likely not wanted since the process is supposed to continue from where it blocked. Between these two macros is the application user code.PROCESS_WAIT_EVENT()
: yield and wait for an event to be posted to this process;PROCESS_WAIT_EVENT_UNTIL(cond)
: yield and wait for an event, and for the cond
condition to be true;PROCESS_WAIT_UNTIL(cond)
: wait until the cond
condition is true. If it is true at the instant of call, does not yield;PROCESS_WAIT_WHILE(cond)
: wait while the cond
condition is true. If it is false at the instant of call, does not yield;PROCESS_PAUSE()
: post an event to itself and yield. This allows the kernel to execute other processes if there are events pending, and ensures continuation of this process afterward;PROCESS_EXIT()
: terminate the process.This second example called 02_event-post implements two processes: the first reads a temperature sensor periodically, and averages the measures over a given number of samples. When a new measure is ready, this process posts an event to another process in charge of printing the result over a serial link. Not only the event is posted (which is a notification), but a pointer to the measured data is passed to the other process as well.
The source files for this example are located in the examples/02_event-post
folder. The main source file is event-post.c
.
Here is the code for this example:
#include "contiki.h" #include "dev/leds.h" #include <stdio.h> /* For printf() */ /* Driver Include */ #include "ds1722.h" /* Variables: the application specific event value */ static process_event_t event_data_ready; /*---------------------------------------------------------------------------*/ /* We declare the two processes */ PROCESS(temp_process, "Temperature process"); PROCESS(print_process, "Print process"); /* We require the processes to be started automatically */ AUTOSTART_PROCESSES(&temp_process, &print_process); /*---------------------------------------------------------------------------*/ /* Implementation of the first process */ PROCESS_THREAD(temp_process, ev, data) { // variables are declared static to ensure their values are kept // between kernel calls. static struct etimer timer; static int count = 0; static int average, valid_measure; // those 3 variables are recomputed at every run, therefore it is not // necessary to declare them static. int measure; uint8_t msb, lsb; // any process mustt start with this. PROCESS_BEGIN(); /* allocate the required event */ event_data_ready = process_alloc_event(); /* Initialize the temperature sensor */ ds1722_init(); ds1722_set_res(10); ds1722_sample_cont(); average = 0; // set the etimer module to generate an event in one second. etimer_set(&timer, CLOCK_CONF_SECOND/4); while (1) { // wait here for the timer to expire PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER); leds_toggle(LEDS_BLUE); // do the process work msb = ds1722_read_MSB(); lsb = ds1722_read_LSB(); measure = ((uint16_t)msb) << 2; measure += (lsb >> 6) & 0x03; average += measure; count ++; if (count == 4) { // average the sum and store valid_measure = average >> 2; // reset variables average = 0; count = 0; // post an event to the print process // and pass a pointer to the last measure as data process_post(&print_process, event_data_ready, &valid_measure); } // reset the timer so it will generate another event etimer_reset(&timer); } // any process must end with this, even if it is never reached. PROCESS_END(); } /*---------------------------------------------------------------------------*/ /* Implementation of the second process */ PROCESS_THREAD(print_process, ev, data) { PROCESS_BEGIN(); while (1) { // wait until we get a data_ready event PROCESS_WAIT_EVENT_UNTIL(ev == event_data_ready); // display it printf("temperature = %u.%u\n", (*(int*)data)>>2, ((*(int*)data)&0x3)*25); } PROCESS_END(); } /*---------------------------------------------------------------------------*/
The main difference with the previous example is that the two processes interact with each other. This is achieved thanks to events, which allow waking up processes while indicating them why they have been woken and optional data is passed to them.
There is a certain number of system-defined events, here is the list:
These events are used by the kernel internally, but may be used in an application with care. Otherwise new events can be defined using the following function:
process_event_t process_alloc_event (void)
Hence declaring a global variable that will receive the new event value can then be used amongst processes, as it is done in the example.
Process interaction is achieved with the following functions:
void process_start (struct process *p, const char *arg)
: this adds a process to the kernel active process list. Processes may be started automatically using the AUTOSTART_PROCESS
macro (see the examples for how to use it), or started manually using this function. The argument p
points to the process variable (declared with the PROCESS
macro), and arg
is the data the process will receive for its initialization call.void process_exit (struct process *p)
: this stops a process and removes it from the kernel active process list. What is really done is: all the other processes will receive an event informing them the process is about to exit, then the process itself is run a last time with the event 'PROCESS_EVENT_EXIT' to let it end correctly, and finally the process is removed from the kernel list.int process_post (struct process *p, process_event_t ev, void *data)
: this function is the most generic, it posts an event to a given process, specifying the event type and a pointer to the data passed. The value returned is PROCESS_ERR_OK
if the event is posted, or PROCESS_ERR_FULL
if the event queue was full and the event could not be posted. If the event is posted, the kernel will execute the process when it will have done so with all the other processes with an event pending.void process_post_synch (struct process *p, process_event_t ev, void *data)
: this function is similar to the previous one, except that if the event can be posted the process will execute now with the event. In other word, it will skip the event waiting queue, and when it returns, the calling process will continue its execution until the next blocking macro call.void process_poll (struct process *p)
: this last function sets the process to be requiring a poll. A poll request is a little different from a event post, because before processing an event, the kernel first processes all the poll request for all the running processes. When it does, the polled process is executed with a PROCESS_EVENT_POLL
event and no data.This third example brings several new features at once. We'll see here how to set up the uIP stack (IPv4 only for now), how to set up SLIP (Serial Line IP) for enabling IP connectivity between a PC running Linux and a WSN430 with a serial link, and how to implement a simple TCP echo server using the protosocket library.
This example is found in the example/03_echo-server
folder, and contains the following files: echo-server.c
for the echo server code, and the Makefile
to build the project. Let's get started…
In order to enable uIP, adding CFLAGS += -DWITH_UIP=1
in the Makefile
file of the project folder is all what's required. But for your information, let's see what is done to make uIP functional. The networking setup is executed in the platform/wsn430/contiki-wsn430-init-net.c
file.
uip
and uip_fw
modules must be initialized by starting their processes:process_start(&tcpip_process, NULL); process_start(&uip_fw_process, NULL);
uip_ipaddr_t hostaddr, netmask; uip_init(); uip_ipaddr(&hostaddr, 172,16, rimeaddr_node_addr.u8[0],rimeaddr_node_addr.u8[1]); uip_ipaddr(&netmask, 255,255,0,0); uip_ipaddr_copy(&meshif.ipaddr, &hostaddr); uip_sethostaddr(&hostaddr); uip_setnetmask(&netmask); uip_over_mesh_set_net(&hostaddr, &netmask);
static struct uip_fw_netif meshif = {UIP_FW_NETIF(172,16,1,0, 255,255,0,0, uip_over_mesh_send)}; static struct uip_fw_netif slipif = {UIP_FW_NETIF(0,0,0,0, 0,0,0,0, slip_send)};
uip
module:uip_over_mesh_set_gateway_netif(&slipif); uip_fw_default(&meshif);
This setup allows the nodes to have 2 network interfaces one for radio communications and one other for serial line communications.
SLIP (Serial Line IP) is build with uIP in every node, but is not activated by default. Only nodes that will receive SLIP packets on their serial line will activate the module, and start using this new interface.
If you use the tunslip
program found in the tools/
directory, you will be able to enable SLIP on the connected node, and send IP packets to this one and the others with the forwarding capability of uIP.
The tunslip.sh
script lauches tunslip
with the default parameters.
The uIP stack is up and running on the gateway node, we have the tunslip network interface ready on the computer, we just have to write the application! The code is in the echo-server.c
file.
The echo server will use a TCP connection. It will listen on port 12345, waiting for connection. Once one is established, it will send a welcome message, then wait for incoming data. Once it has received some it will send it back to the sender and closes the connection.
Here is the code:
#include "contiki.h" #include "contiki-net.h" #include "dev/leds.h" #include <stdio.h> #include <string.h> /* * We define one protosocket since we've decided to only handle one * connection at a time. If we want to be able to handle more than one * connection at a time, each parallell connection needs its own * protosocket. */ static struct psock ps; /* * We must have somewhere to put incoming data, and we use a 50 byte * buffer for this purpose. */ static char buffer[50]; /*---------------------------------------------------------------------------*/ /* * A protosocket always requires a protothread. The protothread * contains the code that uses the protosocket. We define the * protothread here. */ static PT_THREAD(handle_connection(struct psock *p)) { /* * A protosocket's protothread must start with a PSOCK_BEGIN(), with * the protosocket as argument. * * Remember that the same rules as for protothreads apply: do NOT * use local variables unless you are very sure what you are doing! * Local (stack) variables are not preserved when the protothread * blocks. */ PSOCK_BEGIN(p); /* * We start by sending out a welcoming message. The message is sent * using the PSOCK_SEND_STR() function that sends a null-terminated * string. */ PSOCK_SEND_STR(p, "Welcome, please type something and press return.\n"); /* * Next, we use the PSOCK_READTO() function to read incoming data * from the TCP connection until we get a newline character. The * number of bytes that we actually keep is dependant of the length * of the input buffer that we use. Since we only have a 10 byte * buffer here (the buffer[] array), we can only remember the first * 10 bytes received. The rest of the line up to the newline simply * is discarded. */ PSOCK_READTO(p, '\n'); /* * And we send back the contents of the buffer. The PSOCK_DATALEN() * function provides us with the length of the data that we've * received. Note that this length will not be longer than the input * buffer we're using. */ PSOCK_SEND_STR(p, "Got the following data: "); PSOCK_SEND(p, buffer, PSOCK_DATALEN(p)); PSOCK_SEND_STR(p, "Good bye!\r\n"); /* * We close the protosocket. */ PSOCK_CLOSE(p); /* * And end the protosocket's protothread. */ PSOCK_END(p); } /*---------------------------------------------------------------------------*/ /* * We declare the process. */ PROCESS(example_psock_server_process, "Example protosocket server"); AUTOSTART_PROCESSES(&example_psock_server_process); /*---------------------------------------------------------------------------*/ /* * The definition of the process. */ PROCESS_THREAD(example_psock_server_process, ev, data) { /* * The process begins here. */ PROCESS_BEGIN(); /* * We start with setting up a listening TCP port. Note how we're * using the HTONS() macro to convert the port number (1234) to * network byte order as required by the tcp_listen() function. */ tcp_listen(HTONS(12345)); /* * We loop for ever, accepting new connections. */ while(1) { /* * We wait until we get the first TCP/IP event, which probably * comes because someone connected to us. */ PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); /* * If a peer connected with us, we'll initialize the protosocket * with PSOCK_INIT(). */ if(uip_connected()) { /* * The PSOCK_INIT() function initializes the protosocket and * binds the input buffer to the protosocket. */ PSOCK_INIT(&ps, buffer, sizeof(buffer)); /* * We loop until the connection is aborted, closed, or times out. */ while(!(uip_aborted() || uip_closed() || uip_timedout())) { /* * We wait until we get a TCP/IP event. Remember that we * always need to wait for events inside a process, to let * other processes run while we are waiting. */ PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); /* * Here is where the real work is taking place: we call the * handle_connection() protothread that we defined above. This * protothread uses the protosocket to receive the data that * we want it to. */ handle_connection(&ps); } } } /* * We must always declare the end of a process. */ PROCESS_END(); } /*---------------------------------------------------------------------------*/
You can now compile the project, and program a device with it.
For SLIP, assuming you're using the /dev/ttyS0
serial port of your computer, type make
then run ./tunslip.sh
as root in the tools
directory. You should observe the following:
[root@brigand tools]# ./tunslip.sh ./tunslip -s /dev/ttyS0 -B 115200 172.16.0.0 255.255.0.0 slip started on ``/dev/ttyS0'' opened device ``/dev/tun0'' ifconfig tun0 inet `hostname` up route add -net 172.16.0.0 netmask 255.255.0.0 dev tun0 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:194.199.21.252 P-t-P:194.199.21.252 Mask:255.255.255.255 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)
This is waiting for a connection from the WSN430. Power on the WSN430 you've just programmed, already plugged on the serial link, you should see:
Contiki 2.3 started. Rime address is 1.109 MAC ff:00:00:12:91:bf:6d:01 X-MAC channel 26 uIP started with IP address 172.16.1.109 Starting 'Example protosocket server' 1.109: making myself the IP network gateway. IPv4 address of the gateway: 172.16.1.109 route add -net 172.16.1.109 netmask 255.255.255.255 dev tun0
You have the IP of the node (172.16.1.109 here) printed. You can now either ping the device (ping 172.16.1.109
) or connect to the server we've created using netcat (nc 172.16.1.109 12345
)
The example found in the examples/04_echo-udp
folder implements a simple UDP server application. The source code is found in the echo-serve.c
file.
UDP is connectionless, therefore programming a UDP application is rather simple. Only two things can be done with UDP: sending and receiving datagrams. Contiki uses the word 'connection' both for UDP and TCP, but in the sense of a BSD socket. It is an endpoint for Internet communication. Here are the different functions/macros used for programming using UDP:
struct uip_udp_conn * udp_new (const uip_ipaddr_t *ripaddr, u16_t port, void *appstate)
: this function creates a new UDP 'connection'. If the connection is used to send data to a specific host, the arguments ripaddr
(remote IP address) and port
must be filled adequately, otherwise ripaddr
may be set to NULL
and port to 0 in order to accept any incoming datagram. appstate
is a pointer to some data that will be passed to the process when UDP events will occur. The function returns a pointer to the connection, that will be used by all the other functions afterward. The function chooses a local port number arbitrarily, which can be overwritten as explained below;udp_bind(conn, port)
: this macro binds an open UDP connection to a local port number. The specified port will be the source port used to send datagrams as well as the listening port for incoming datagrams;uip_newdata()
: this macro indicates if there is data available on the connection. If so it can be accessed from the pointer uip_appdata
and the length obtained by uip_datalen()
.The Echo Server UDP uses most of these functions. It listens on UDP port 50000 and replies back incoming datagrams. The complete source code of the application is here:
/** * \file * A network application that listens on UDP port 50000 and echoes. * \author * Clément Burin des Roziers <clement.burin-des-roziers@inrialpes.fr> */ #include "contiki.h" #include "contiki-net.h" #include "dev/leds.h" #include <stdio.h> #include <string.h> #define PRINTF printf #define UDP_DATA_LEN 120 #define UDP_HDR ((struct uip_udpip_hdr *)&uip_buf[UIP_LLH_LEN]) static struct uip_udp_conn *udpconn; static uint8_t udpdata[UDP_DATA_LEN] = "rx="; static void udphandler(process_event_t ev, process_data_t data) { if (uip_newdata()) { /* Set the last byte of the received data as 0 in order to print it. */ int len = uip_datalen(); ((char *)uip_appdata)[len] = 0; printf("Received from %u.%u.%u.%u:%u: '%s'\n", uip_ipaddr_to_quad(&UDP_HDR->srcipaddr), HTONS(UDP_HDR->srcport), (char*)uip_appdata); /* Prepare the response datagram in a local buffer */ memcpy(udpdata, "rx=", 3); memcpy(udpdata+3, uip_appdata, len); /* Copy the information about the sender to the udpconn in order to reply */ uip_ipaddr_copy(&udpconn->ripaddr , &UDP_HDR->srcipaddr); // ip address udpconn->rport = UDP_HDR->srcport; // UDP port /* Send the reply datagram */ printf("sending back\n"); uip_udp_packet_send(udpconn, udpdata, uip_datalen()+3); /* Restore the udpconn to previous setting in order to receive other packets */ uip_ipaddr_copy(&udpconn->ripaddr , &uip_all_zeroes_addr); udpconn->rport = 0; } } /*---------------------------------------------------------------------------*/ /* * We declare the process. */ PROCESS(example_udp_server_process, "Example UDP server"); AUTOSTART_PROCESSES(&example_udp_server_process); /*---------------------------------------------------------------------------*/ /* * The definition of the process. */ PROCESS_THREAD(example_udp_server_process, ev, data) { uip_ipaddr_t ipaddr; PROCESS_BEGIN(); printf("UDP Echo Server test\n"); /* Create a UDP 'connection' with IP 0.0.0.0 and port 0 as remote host. * This means the stack will accepts UDP datagrams from any node. */ udpconn = udp_new(NULL, HTONS(0), NULL); /* Bind the UDP 'connection' to the port 50000. That's the port we're listening on. */ udp_bind(udpconn, HTONS(50000)); printf("listening on UDP port %u\n", HTONS(udpconn->lport)); while(1) { /* Wait until we have an event caused by tcpip interaction */ PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); /* Handle it */ udphandler(ev, data); } PROCESS_END(); } /*---------------------------------------------------------------------------*/
The application starts in the process definition. First a UDP connection is created, specifying remote IP address and port as 0, indicating it's a listening connection. Then the local UDP port is bound to 50000, which means the connection listens on that port. Finally, an infinite loop is entered waiting for TCPIP events, and calling a handler function when these occur.
The handler function checks if new data is available from the uIP buffer. If so, it prints the received data for debug as well as the sender IP address and port, then prepare a response datagram by prepending „rx=“ to the received message. The connection information must be updated in order to send the response to the emitter of the message. Therefore the ripaddr
and rport
fields of the UDP connection structure are filled with the sender information extracted from the received IP packet. Then the function for sending the packet is invoked. Finally the UDP connection structure is updated back to its original state (with remote IP address and port set to 0) in order to be able to receive datagrams from any host.
Run tunslip as in the previous example and execute nc -u 176.12.xxx.xxx 50000
in a terminal to test the echo server.
The examples found in the 05_ipv6
folder implement a UDP-server and a UDP-client applications, using UDP and IPv6. The first example will run on a node, listening on a given UDP port and waiting fir datagrams to arrive. The second example will send datagrams to the server periodically.
To enable IPv6, the following line must be added to the project's Makefile:
UIP_CONF_IPV6=1
Here is the code for the server example:
/* * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file is part of the Contiki operating system. * */ #include "contiki.h" #include "contiki-lib.h" #include "contiki-net.h" #include <string.h> #define DEBUG 1 #if DEBUG #include <stdio.h> #define PRINTF(...) printf(__VA_ARGS__) #define PRINT6ADDR(addr) PRINTF(" %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x ", ((u8_t *)addr)[0], ((u8_t *)addr)[1], ((u8_t *)addr)[2], ((u8_t *)addr)[3], ((u8_t *)addr)[4], ((u8_t *)addr)[5], ((u8_t *)addr)[6], ((u8_t *)addr)[7], ((u8_t *)addr)[8], ((u8_t *)addr)[9], ((u8_t *)addr)[10], ((u8_t *)addr)[11], ((u8_t *)addr)[12], ((u8_t *)addr)[13], ((u8_t *)addr)[14], ((u8_t *)addr)[15]) #define PRINTLLADDR(lladdr) PRINTF(" %02x:%02x:%02x:%02x:%02x:%02x ",(lladdr)->addr[0], (lladdr)->addr[1], (lladdr)->addr[2], (lladdr)->addr[3],(lladdr)->addr[4], (lladdr)->addr[5]) #else #define PRINTF(...) #define PRINT6ADDR(addr) #define PRINTLLADDR(addr) #endif #define UDP_IP_BUF ((struct uip_udpip_hdr *)&uip_buf[UIP_LLH_LEN]) #define MAX_PAYLOAD_LEN 120 static struct uip_udp_conn *server_conn; PROCESS(udp_server_process, "UDP server process"); AUTOSTART_PROCESSES(&udp_server_process); /*---------------------------------------------------------------------------*/ static void tcpip_handler(void) { static int seq_id; char buf[MAX_PAYLOAD_LEN]; if(uip_newdata()) { ((char *)uip_appdata)[uip_datalen()] = 0; PRINTF("Server received: '%s' from ", (char *)uip_appdata); PRINT6ADDR(&UDP_IP_BUF->srcipaddr); PRINTF("\n"); uip_ipaddr_copy(&server_conn->ripaddr, &UDP_IP_BUF->srcipaddr); server_conn->rport = UDP_IP_BUF->srcport; PRINTF("Responding with message: "); sprintf(buf, "Hello from the server! (%d)", ++seq_id); PRINTF("%s\n", buf); uip_udp_packet_send(server_conn, buf, strlen(buf)); /* Restore server connection to allow data from any node */ memset(&server_conn->ripaddr, 0, sizeof(server_conn->ripaddr)); server_conn->rport = 0; } } /*---------------------------------------------------------------------------*/ static void print_local_addresses(void) { int i; uip_netif_state state; PRINTF("Server IPv6 addresses: \n"); for(i = 0; i < UIP_CONF_NETIF_MAX_ADDRESSES; i++) { state = uip_netif_physical_if.addresses[i].state; if(state == TENTATIVE || state == PREFERRED) { PRINT6ADDR(&uip_netif_physical_if.addresses[i].ipaddr); PRINTF("\n"); } } } /*---------------------------------------------------------------------------*/ PROCESS_THREAD(udp_server_process, ev, data) { static struct etimer timer; PROCESS_BEGIN(); PRINTF("UDP server started\n"); // wait 3 second, in order to have the IP addresses well configured etimer_set(&timer, CLOCK_CONF_SECOND*3); // wait until the timer has expired PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER); print_local_addresses(); // set NULL and 0 as IP address and port to accept packet from any node and any srcport. server_conn = udp_new(NULL, HTONS(0), NULL); udp_bind(server_conn, HTONS(3000)); PRINTF("Server listening on UDP port %u\n", HTONS(server_conn->lport)); while(1) { PROCESS_YIELD(); if(ev == tcpip_event) { tcpip_handler(); } } PROCESS_END(); } /*---------------------------------------------------------------------------*/
And here's the code for the client example:
/* * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file is part of the Contiki operating system. * */ #include "contiki.h" #include "contiki-lib.h" #include "contiki-net.h" #include <string.h> #define DEBUG 1 #if DEBUG #include <stdio.h> #define PRINTF(...) printf(__VA_ARGS__) #define PRINT6ADDR(addr) PRINTF(" %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x ", ((u8_t *)addr)[0], ((u8_t *)addr)[1], ((u8_t *)addr)[2], ((u8_t *)addr)[3], ((u8_t *)addr)[4], ((u8_t *)addr)[5], ((u8_t *)addr)[6], ((u8_t *)addr)[7], ((u8_t *)addr)[8], ((u8_t *)addr)[9], ((u8_t *)addr)[10], ((u8_t *)addr)[11], ((u8_t *)addr)[12], ((u8_t *)addr)[13], ((u8_t *)addr)[14], ((u8_t *)addr)[15]) #define PRINTLLADDR(lladdr) PRINTF(" %02x:%02x:%02x:%02x:%02x:%02x ",(lladdr)->addr[0], (lladdr)->addr[1], (lladdr)->addr[2], (lladdr)->addr[3],(lladdr)->addr[4], (lladdr)->addr[5]) #else #define PRINTF(...) #define PRINT6ADDR(addr) #define PRINTLLADDR(addr) #endif #define SEND_INTERVAL 15 * CLOCK_SECOND #define MAX_PAYLOAD_LEN 40 static struct uip_udp_conn *client_conn; /*---------------------------------------------------------------------------*/ PROCESS(udp_client_process, "UDP client process"); AUTOSTART_PROCESSES(&udp_client_process); /*---------------------------------------------------------------------------*/ static void tcpip_handler(void) { char *str; if(uip_newdata()) { str = uip_appdata; str[uip_datalen()] = '\0'; printf("Response from the server: '%s'\n", str); } } /*---------------------------------------------------------------------------*/ static void timeout_handler(void) { static int seq_id; char buf[MAX_PAYLOAD_LEN]; printf("Client sending to: "); PRINT6ADDR(&client_conn->ripaddr); sprintf(buf, "Hello %d from the client", ++seq_id); printf(" (msg: %s)\n", buf); uip_udp_packet_send(client_conn, buf, strlen(buf)); } /*---------------------------------------------------------------------------*/ static void print_local_addresses(void) { int i; uip_netif_state state; PRINTF("Client IPv6 addresses: "); for(i = 0; i < UIP_CONF_NETIF_MAX_ADDRESSES; i++) { state = uip_netif_physical_if.addresses[i].state; if(state == TENTATIVE || state == PREFERRED) { PRINT6ADDR(&uip_netif_physical_if.addresses[i].ipaddr); PRINTF("\n"); } } } /*---------------------------------------------------------------------------*/ static void set_connection_address(uip_ipaddr_t *ipaddr) { // change this IP address depending on the node that runs the server! uip_ip6addr(ipaddr,0xfe80,0,0,0,0x8400,0x0012,0x91c7,0x1b01); } /*---------------------------------------------------------------------------*/ PROCESS_THREAD(udp_client_process, ev, data) { static struct etimer et; uip_ipaddr_t ipaddr; PROCESS_BEGIN(); PRINTF("UDP client process started\n"); // wait 3 second, in order to have the IP addresses well configured etimer_set(&et, CLOCK_CONF_SECOND*3); // wait until the timer has expired PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER); print_local_addresses(); set_connection_address(&ipaddr); /* new connection with remote host */ client_conn = udp_new(&ipaddr, HTONS(3000), NULL); PRINTF("Created a connection with the server "); PRINT6ADDR(&client_conn->ripaddr); PRINTF("local/remote port %u/%u\n", HTONS(client_conn->lport), HTONS(client_conn->rport)); etimer_set(&et, SEND_INTERVAL); while(1) { PROCESS_YIELD(); if(etimer_expired(&et)) { timeout_handler(); etimer_restart(&et); } else if(ev == tcpip_event) { tcpip_handler(); } } PROCESS_END(); } /*---------------------------------------------------------------------------*/
The IPv6 local-link addresses of the nodes are determined automatically using Stateless Address Autoconfiguration. Each node appends its link-layer 6byte address to the local-link network prefix (fe80::0/64).
When the tcpip_process
is started, the address configuration is done, then some IPv6 ND messages are exchanged (such as Duplicate Address Detection and Router Sollicitation). If a router is present, it may give some extra addresses to the node.The stack is now ready to operate at least on a local link scope.
The procedure to send and receive UDP datagrams is now similar to when IPv4 was used. The UDP connection must be filled with the other node's IP address and the destination port used, and the local port number may be specified. Then the functions called and events are the same.
To try the communication between 2 nodes, make sure that you update the udp-client.c
with the server IP address.
If you want to interact with your node UDP server from a PC, you can use the tools/uip6-bridge
application on a node linked to the PC with a serial link. The PC should run tapslip6 and be configure to act as an IPv6 router. The procedure is described in the tools/uip6-bridge/README
file.