Simple client to illustrate the functionality of cantcoap library. The client sits in a while loop sending CoAP PUTs to a remote server and prints each acknowledgement.

Dependencies:   VodafoneUSBModem cantcoap mbed-rtos mbed

Teaser

CoAP implementation that focuses on simplicity by offering a minimal set of functions and straightforward interface.

	CoapPDU *pdu = new CoapPDU();
	pdu->setType(CoapPDU::COAP_CONFIRMABLE);
	pdu->setCode(CoapPDU::COAP_GET);
	pdu->setToken((uint8_t*)"\3\2\1\0",4);
	pdu->setMessageID(0x0005);
	pdu->setURI((char*)"test",4);

	// send packet 
	ret = send(sockfd,pdu->getPDUPointer(),pdu->getPDULength(),0);

...

	// receive packet
	ret = recvfrom(sockfd,&buffer,BUF_LEN,0,(sockaddr*)&recvAddr,&recvAddrLen);
	CoapPDU *recvPDU = new CoapPDU((uint8_t*)buffer,ret);
	if(recvPDU->validate()) {
		recvPDU->getURI(uriBuffer,URI_BUF_LEN,&recvURILen);
		...
	}

Long description

This is a CoAP implementation with a focus on simplicity. The library only provides PDU construction and de-construction.

The user is expected to deal with retransmissions, timeouts, and message ID matching themselves. This isn’t as arduous as it sounds and makes a lot more sense on a constrained device.

Imagine for example a simple microcontroller sensor that only reports readings once every 15 minutes, and only sends a few packets each time. Do you really need a complicated framework to deal with acknowledgements and re-transmissions?

Since CoAP recommends you only send one packet at at time, this means you only need to keep track of one on-going transaction at a time. Yeah... I think you’re capable of this.

Furthermore, the timers and interrupt processes between different embedded processor architectures, vary quite a bit. So it often makes sense to write the packet sending processes yourself.

Finally, you might be sending the packets over odd transport bearers such as a SMS (woah dude, that's just totally wild) or a simple radio bearer. In which case, it’s easiest to deal with buffers. If I built retransmission handlers, they’d all be UDP/IP specific and would bloat the code for no reason.

Resources

Github page for the cantcoap library is here:

https://github.com/staropram/cantcoap

Doxygen for the library is here:

http://staropram.github.io/cantcoap/index.html

Examples

Construction

There are a couple of different ways to construct a PDU depending on whether you want the library to allocate memory for you, or whether you have an external buffer you want to use. You can also re-purpose existing objects.

Using a managed object

The simplest usage scenario hands control of memory allocation to the library:

CoapPDU *pdu = new CoapPDU();
...
pdu->setType(CoapPDU::COAP_CONFIRMABLE);
pdu->setCode(CoapPDU::COAP_GET);
pdu->addOption(11,5,(uint8_t*)"hello");
pdu->addOption(11,5,(uint8_t*)"there");
pdu->addOption(11,6,(uint8_t*)"server");

In this case you just call the default constructor. That's it. The library handles memory from there-on out. For example, when adding each of those options, the library will realloc the pdu to accomodate space for them. It will also shrink the PDU if something changes (like the token length) so that it always uses the minimum amount of memory.

When you free the PDU, all data including the buffer is deleted. The PDU can also be reused as shown below.

Using an external buffer for memory

There are two obvious reasons why you would do this:

1. The buffer contains a CoAP PDU and you want to access the data in the PDU. 2. Buffers cost space and allocating memory consumes processor resources. On embedded targets it is often simpler to reuse buffers where possible.

The first instance is a special case and requires some extra work. Just using an external buffer is as simple as follows:

uint8_t *buffer[100];
CoapPDU *pdu = new CoapPDU((uint8_t*)buffer,100,0);
...
pdu->setType(CoapPDU::COAP_CONFIRMABLE);
pdu->setCode(CoapPDU::COAP_GET);
pdu->addOption(11,5,(uint8_t*)"hello");
pdu->addOption(11,5,(uint8_t*)"there");
pdu->addOption(11,6,(uint8_t*)"server");

The PDU is constructed as normal except that the memory of your buffer is used instead of allocated memory.

A call such as this:

pdu->addOption(11,5,(uint8_t*)"hello");

Will fail if there is no space left in the buffer.

When you delete the object, the buffer is not freed. Hey, it's your buffer mannn!

Reusing an existing object

Regardless of whether you constructed a PDU using either of the above methods, you can always reuse it:

pdu->reset(); 
...
pdu->setType(CoapPDU::COAP_CONFIRMABLE);
pdu->setCode(CoapPDU::COAP_GET);
pdu->addOption(11,5,(uint8_t*)"hello");
pdu->addOption(11,5,(uint8_t*)"there");
pdu->addOption(11,6,(uint8_t*)"server");

The only difference is that if the PDU was initially constructed using managed-memory, then it will continue to have managed-memory. Whereas if the PDU was constructed with an external buffer, then you are limited in space by the size of the buffer you used.

Receving CoAP packets over a network or something

In this case you have a CoAP PDU in a buffer you just gobbled from a socket and want to read it:

uint8_t *buffer[100];
int ret = recvfrom(sockfd,&buffer,BUF_LEN,0,(sockaddr*)&recvAddr,&recvAddrLen);
CoapPDU *recvPDU = new CoapPDU((uint8_t*)buffer,ret,100);
if(recvPDU->validate()) {
	recvPDU->printHuman();
	// do your work
}

You must call CoapPDU::validate() and get a positive response before accessing any of the data members. This sets up some internal pionters and so on, so if you fail to do it, undefined behaviour will result.

Note that the constructor is just a shorthand for the external-buffer-constructor explained above, and you can use the long form if you want. For example. you might want to use the long form if you have a buffer bigger than the PDU and you expect to reuse it.

You can reuse this object by resetting it as above.

If you reuse such an object, you need to set the PDU length manually because there is no way to deduce the PDU length using validate():

	// earlier
	#define BUFLEN 500
	char buffer[BUFLEN];
	CoapPDU *recvPDU = new CoapPDU((uint8_t*)buffer,BUFLEN,BUFLEN);

	...

	while(1) {
		// receive packet
		ret = sockfd,&buffer,BUFLEN,0);
		if(ret==-1) {
			INFO("Error receiving data");
			// handle error
		}

		// validate packet
		// you should also check that ret doesn't exceed buffer length
		recvPDU->setPDULength(ret);
		if(recvPDU->validate()!=1) {
			INFO("Malformed CoAP packet");
			// handle error
		}
	}
Committer:
ashleymills
Date:
Tue Oct 08 14:53:13 2013 +0000
Revision:
0:b2408eb82167
Init.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
ashleymills 0:b2408eb82167 1 /// Simple example making use of cantcoap library, just keeps sending packets to a server and prints the response.
ashleymills 0:b2408eb82167 2
ashleymills 0:b2408eb82167 3 #include "rtos.h"
ashleymills 0:b2408eb82167 4 #include "mbed.h"
ashleymills 0:b2408eb82167 5 #include "VodafoneUSBModem.h"
ashleymills 0:b2408eb82167 6 #include "cantcoap.h"
ashleymills 0:b2408eb82167 7 #include "bsd_socket.h"
ashleymills 0:b2408eb82167 8
ashleymills 0:b2408eb82167 9 DigitalOut myled(LED1);
ashleymills 0:b2408eb82167 10
ashleymills 0:b2408eb82167 11 void fail(int code) {
ashleymills 0:b2408eb82167 12 while(1) {
ashleymills 0:b2408eb82167 13 myled = !myled;
ashleymills 0:b2408eb82167 14 Thread::wait(100);
ashleymills 0:b2408eb82167 15 }
ashleymills 0:b2408eb82167 16 }
ashleymills 0:b2408eb82167 17
ashleymills 0:b2408eb82167 18 #define APN_GDSP
ashleymills 0:b2408eb82167 19 //#define APN_CONTRACT
ashleymills 0:b2408eb82167 20
ashleymills 0:b2408eb82167 21 #ifdef APN_GDSP
ashleymills 0:b2408eb82167 22 #define APN "ppinternetd.gdsp"
ashleymills 0:b2408eb82167 23 #define APN_USERNAME ""
ashleymills 0:b2408eb82167 24 #define APN_PASSWORD ""
ashleymills 0:b2408eb82167 25 #endif
ashleymills 0:b2408eb82167 26
ashleymills 0:b2408eb82167 27 #ifdef APN_CONTRACT
ashleymills 0:b2408eb82167 28 #define APN "internet"
ashleymills 0:b2408eb82167 29 #define APN_USERNAME "web"
ashleymills 0:b2408eb82167 30 #define APN_PASSWORD "web"
ashleymills 0:b2408eb82167 31 #endif
ashleymills 0:b2408eb82167 32
ashleymills 0:b2408eb82167 33 sockaddr_in bindAddr,serverAddress;
ashleymills 0:b2408eb82167 34 bool connectToSocketUDP(char *ipAddress, int port, int *sockfd) {
ashleymills 0:b2408eb82167 35 *sockfd = -1;
ashleymills 0:b2408eb82167 36 // create the socket
ashleymills 0:b2408eb82167 37 if((*sockfd=socket(AF_INET,SOCK_DGRAM,0))<0) {
ashleymills 0:b2408eb82167 38 DBG("Error opening socket");
ashleymills 0:b2408eb82167 39 return false;
ashleymills 0:b2408eb82167 40 }
ashleymills 0:b2408eb82167 41 socklen_t sockAddrInLen = sizeof(struct sockaddr_in);
ashleymills 0:b2408eb82167 42
ashleymills 0:b2408eb82167 43 // bind socket to 11111
ashleymills 0:b2408eb82167 44 memset(&bindAddr, 0x00, sockAddrInLen);
ashleymills 0:b2408eb82167 45 bindAddr.sin_family = AF_INET; // IP family
ashleymills 0:b2408eb82167 46 bindAddr.sin_port = htons(port);
ashleymills 0:b2408eb82167 47 bindAddr.sin_addr.s_addr = IPADDR_ANY; // 32 bit IP representation
ashleymills 0:b2408eb82167 48 // call bind
ashleymills 0:b2408eb82167 49 if(bind(*sockfd,(const struct sockaddr *)&bindAddr,sockAddrInLen)!=0) {
ashleymills 0:b2408eb82167 50 DBG("Error binding socket");
ashleymills 0:b2408eb82167 51 perror(NULL);
ashleymills 0:b2408eb82167 52 }
ashleymills 0:b2408eb82167 53
ashleymills 0:b2408eb82167 54 INFO("UDP socket created and bound to: %s:%d",inet_ntoa(bindAddr.sin_addr),ntohs(bindAddr.sin_port));
ashleymills 0:b2408eb82167 55
ashleymills 0:b2408eb82167 56 // create the socket address
ashleymills 0:b2408eb82167 57 memset(&serverAddress, 0x00, sizeof(struct sockaddr_in));
ashleymills 0:b2408eb82167 58 serverAddress.sin_addr.s_addr = inet_addr(ipAddress);
ashleymills 0:b2408eb82167 59 serverAddress.sin_family = AF_INET;
ashleymills 0:b2408eb82167 60 serverAddress.sin_port = htons(port);
ashleymills 0:b2408eb82167 61
ashleymills 0:b2408eb82167 62 // do socket connect
ashleymills 0:b2408eb82167 63 //LOG("Connecting socket to %s:%d", inet_ntoa(serverAddress.sin_addr), ntohs(serverAddress.sin_port));
ashleymills 0:b2408eb82167 64 if(connect(*sockfd, (const struct sockaddr *)&serverAddress, sizeof(serverAddress))<0) {
ashleymills 0:b2408eb82167 65 shutdown(*sockfd,SHUT_RDWR);
ashleymills 0:b2408eb82167 66 close(*sockfd);
ashleymills 0:b2408eb82167 67 DBG("Could not connect");
ashleymills 0:b2408eb82167 68 return false;
ashleymills 0:b2408eb82167 69 }
ashleymills 0:b2408eb82167 70 return true;
ashleymills 0:b2408eb82167 71 }
ashleymills 0:b2408eb82167 72
ashleymills 0:b2408eb82167 73 int main() {
ashleymills 0:b2408eb82167 74 DBG_INIT();
ashleymills 0:b2408eb82167 75 DBG_SET_SPEED(115200);
ashleymills 0:b2408eb82167 76 DBG_SET_NEWLINE("\r\n");
ashleymills 0:b2408eb82167 77
ashleymills 0:b2408eb82167 78 // structure for getting address of incoming packets
ashleymills 0:b2408eb82167 79 sockaddr_in fromAddr;
ashleymills 0:b2408eb82167 80 socklen_t fromAddrLen = sizeof(struct sockaddr_in);
ashleymills 0:b2408eb82167 81 memset(&fromAddr,0x00,fromAddrLen);
ashleymills 0:b2408eb82167 82
ashleymills 0:b2408eb82167 83 // connect to cellular network
ashleymills 0:b2408eb82167 84 VodafoneUSBModem modem;
ashleymills 0:b2408eb82167 85 modem.connect(APN,APN_USERNAME,APN_PASSWORD);
ashleymills 0:b2408eb82167 86
ashleymills 0:b2408eb82167 87 // setup socket to remote server
ashleymills 0:b2408eb82167 88 int sockfd = NULL;
ashleymills 0:b2408eb82167 89 if(!connectToSocketUDP("109.74.199.96", 5683, &sockfd)) {
ashleymills 0:b2408eb82167 90 DBG("Error connecting to socket");
ashleymills 0:b2408eb82167 91 fail(1);
ashleymills 0:b2408eb82167 92 }
ashleymills 0:b2408eb82167 93 DBG("\"Connected\" to UDP socket");
ashleymills 0:b2408eb82167 94
ashleymills 0:b2408eb82167 95 // construct CoAP packet
ashleymills 0:b2408eb82167 96 CoapPDU *pdu = new CoapPDU();
ashleymills 0:b2408eb82167 97 pdu->setVersion(1);
ashleymills 0:b2408eb82167 98 pdu->setType(CoapPDU::COAP_CONFIRMABLE);
ashleymills 0:b2408eb82167 99 pdu->setCode(CoapPDU::COAP_PUT);
ashleymills 0:b2408eb82167 100 pdu->setToken((uint8_t*)"\3\2\1\1",4);
ashleymills 0:b2408eb82167 101 pdu->setURI((char*)"test",4);
ashleymills 0:b2408eb82167 102
ashleymills 0:b2408eb82167 103 #define BUFLEN 128
ashleymills 0:b2408eb82167 104 short messageID = 0;
ashleymills 0:b2408eb82167 105 char buffer[BUFLEN];
ashleymills 0:b2408eb82167 106
ashleymills 0:b2408eb82167 107 // reuse the below coap object
ashleymills 0:b2408eb82167 108 CoapPDU *recvPDU = new CoapPDU((uint8_t*)buffer,BUFLEN,BUFLEN);
ashleymills 0:b2408eb82167 109
ashleymills 0:b2408eb82167 110 // send packets to server
ashleymills 0:b2408eb82167 111 while(1) {
ashleymills 0:b2408eb82167 112 // set PDU parameters
ashleymills 0:b2408eb82167 113 pdu->setMessageID(messageID++);
ashleymills 0:b2408eb82167 114
ashleymills 0:b2408eb82167 115 // send packet
ashleymills 0:b2408eb82167 116 int ret = send(sockfd,pdu->getPDUPointer(),pdu->getPDULength(),0);
ashleymills 0:b2408eb82167 117 if(ret!=pdu->getPDULength()) {
ashleymills 0:b2408eb82167 118 INFO("Error sending packet.");
ashleymills 0:b2408eb82167 119 perror(NULL);
ashleymills 0:b2408eb82167 120 fail(2);
ashleymills 0:b2408eb82167 121 }
ashleymills 0:b2408eb82167 122 INFO("Packet sent");
ashleymills 0:b2408eb82167 123
ashleymills 0:b2408eb82167 124 // receive response
ashleymills 0:b2408eb82167 125 ret = recvfrom(sockfd,&buffer,BUFLEN,0,(struct sockaddr *)&fromAddr,&fromAddrLen);
ashleymills 0:b2408eb82167 126 if(ret==-1) {
ashleymills 0:b2408eb82167 127 INFO("Error receiving data");
ashleymills 0:b2408eb82167 128 fail(3);
ashleymills 0:b2408eb82167 129 }
ashleymills 0:b2408eb82167 130 INFO("Received %d bytes from %s:%d",ret,inet_ntoa(fromAddr.sin_addr),ntohs(fromAddr.sin_port));
ashleymills 0:b2408eb82167 131
ashleymills 0:b2408eb82167 132 if(ret>BUFLEN) {
ashleymills 0:b2408eb82167 133 INFO("PDU too large to fit in pre-allocated buffer");
ashleymills 0:b2408eb82167 134 continue;
ashleymills 0:b2408eb82167 135 }
ashleymills 0:b2408eb82167 136
ashleymills 0:b2408eb82167 137 // validate packet
ashleymills 0:b2408eb82167 138 recvPDU->setPDULength(ret);
ashleymills 0:b2408eb82167 139 if(recvPDU->validate()!=1) {
ashleymills 0:b2408eb82167 140 INFO("Malformed CoAP packet");
ashleymills 0:b2408eb82167 141 fail(4);
ashleymills 0:b2408eb82167 142 }
ashleymills 0:b2408eb82167 143 INFO("Valid CoAP PDU received");
ashleymills 0:b2408eb82167 144 recvPDU->printHuman();
ashleymills 0:b2408eb82167 145
ashleymills 0:b2408eb82167 146 Thread::wait(5000);
ashleymills 0:b2408eb82167 147 }
ashleymills 0:b2408eb82167 148 }