Lab-53: AMQP 1.0 protocol

1.0 Introduction

In the series of IoT protocol, in this lab we will go over AMQP protocol theory and then demo using RabbitMQ an open source AMQP broker and Qpid Proton as AMQP client. AMQP stands for Advanced Messaging Queue Protocol. AMQP 1.0 is the primary protocol used in Azure IoT hub.

AMQP 1.0 and pre AMQP 1.0 are totally different protocols. AMQP 1.0 is a standard protocols while pre-AMQP 1.0 are drafts. You can download AMQP 1.0 standard from here. You can read the difference between AMQP 1.0 and pre-AMQP 1.0 here

AMQP is a publish and subscribe protocol. In a typical pub sub protocol you have a producer, a broker and a consumer. The job of producer is to generate message. Broker store the messages in a queue until consumer comes and read the messages. Pub sub protocol decouple producer from consumer, what this mean is producer doesn’t know about consumer and consumer doesn’t know about producer. Consumer can be offline while producer generates message and whenever it comes online it read messages.

AMQP is a wire protocol which means it provides rules for data encoding on the wire. Let’s start from bottom up that’s how AMQP standard organized. I will start with how data is encoded, then the concept of container/node and various endpoints and  finally message transfer

2.0 AMQP Types

AMQP defines type system for encoding data. AMQP supports 4 types. In this tutorial we will go over Primitive and Described types

  1. Primitive types
  2. Described types
  3. Compound types
  4. Restricted types

Primitive types

AMQP defines Primitive types for encoding common scalar values and common collections.

  • The scalar types include booleans, integral numbers, floating point numbers, timestamps, UUIDs, characters, strings, binary data, and symbols.
  • The collection types include arrays and lists

An AMQP encoded data stream consists of untyped bytes with embedded constructors. The embedded constructor indicates how to interpret the untyped bytes that follow.

Let’s see how variable string and fixed data encoded using Primitive types

Variable width encoding:

The size of variable-width data is determined based on an encoded size that prefixes the data. The width of the encoded size is determined by the subcategory of the format code for the variable width value

Below example show how to encode a string “Hello Glorious Messaging World” using Primitive type. The first byte is a constructor 0xA1, which represents variable width data with one octet for data length (the size of length field is one octet). Next to constructor byte is length of data in this case 0x1E

amqp_1

Below is the breakdown of Constructor with 4 bits of subcategory and 4 bits of subtype.

amqp_2

Subcategory is defined in this table. There is fixed width subcategory to encode numbers and variable width to encode strings

amqp_4

Let’s see how it looks on wire. Below show Wireshark capture of variable width encoded string “Hello Glorious Messaging World”. As you can see encoded string starts with constructor 0xa1 then length filed of one octet 0x1e and then string

amqp_3

Fixed width encoding:

The size of fixed-width data is determined based solely on the subcategory of the format code for the fixed width value.

Below is an example fixed width encoding. In case of fixed width encoding there is no length octet after constructor. The length can be computed from subcategory. It is encoded using fixed width subcategory 0x54 (smallint) which mean one octet of data

amqp_5

Described types

AMQP provides a means to annotate Primitive types with descriptor to create described types. Described types is a way to create custom types

– A descriptor defines how to produce a domain specific type from an AMQP primitive value

amqp_6

 

amqp_7

 

3.0 AMQP Frame Transfer

AMQP network consists of nodes connected via links. Nodes are named entities responsible for the safe storage and/or delivery of messages. Messages can originate from, terminate at, or be relayed by nodes. A link is a unidirectional route between two nodes. A link attaches to a node at a terminus. There are two kinds of terminus: sources and targets. A terminus is responsible for tracking the state of a particular stream of incoming or outgoing messages. Sources track outgoing messages and targets track incoming messages. Messages only travel along a link if they meet the entry criteria at the source. As a message travels through an AMQP network, the responsibility for safe storage and delivery of the message is transferred between the nodes it encounters.

Container: is an application, a client app or a broker

Node: is a message producer or consumer. Within an application you can have process which produces messages and process which consume messages

Connection: is a tcp connection between two containers

Channel: A connection is divided into multiple unidirectional channels

Session: Is a logical binding of channels between two nodes. Two unidirectional channels form a bi-directional session. A connection can have multiple sessions

Link: are unidirectional  between two sessions. A session can have multiple links. Within a session Links are uniquely identified by link handle. Links contains target of message delivery

amqp_8

Details on above entities

Connection:

A Connection may contain multiple independent Sessions, up to the negotiated channel numbers

Connection opening:

A connection opened between two containers by sending Open frame. First a TCP connection is established and header frame exchanged to negotiate AMQP version. After that each peer send Open frame to setup connection

  • After establishing or accepting a TCP connection and sending the protocol header, each peer MUST send an open frame before sending any other frames. The open frame describes the capabilities and limits of that peer. The open frame can only be sent on channel 0.
  • After sending the open frame and reading its partner’s open frame a peer MUST operate within mutually acceptable limitations from this point forward

amqp_16

Connection idle timeout

Connections are subject to idle timeout threshold. Timeout is triggered by local peer if no AMQP frames are received after threshold exceeded.The idle timeout is measured in milliseconds, and starts from the time the last frame is received. If the threshold is exceeded, then a peer SHOULD try to gracefully close the connection using a close frame with an error explaining why. If the remote peer does not respond gracefully within a threshold to this, then the peer MAY close the TCP socket

At connection open each peer communicates the maximum period between activity (frames) on the connection that it desires from its partner

amqp_22

Session:

An AMQP  session binds together two unidirectional channels to form a bidirectional, sequential conversation between two containers. Session is created using Begin performative and deleted using End performative

Establishing a session:

  • A Session is established by creating a session endpoint and assigning it an un-used channel number. Sending begin and announcing association of session endpoint with outgoing channel number
  • Upon receiving the begin the partner will check the remote-channel field and find it empty. This indicates that the begin is referring to remotely initiated session
  • The partner will therefore allocate an unused outgoing channel for the remotely initiated session and indicate this by sending its own begin setting the remote-channel field to the incoming channel of the remotely initiated session

amqp_17

Link:

Within a Session a Link is a unidirectional route between a source and a target node, one at each end of the Connection. Links are the paths over which messages are transferred.

Links are unidirectional. Pairs of links are bound to create full duplex communication channels between endpoints. A single Session may be associated with any number of Links.

Links provide a credit-based flow control scheme based on the number of messages transmitted, allowing applications to control which nodes to receive messages from at a given point (e.g., to explicitly fetch a message from a given queue).

Link contains source and target names these are the names of queues or topics in the partner

Links are created using performative attached and destroyed using detach

A link attaches to a node at a terminus. There are two kinds of terminus: sources and targets. A terminus is responsible for tracking the state of a particular stream of incoming or outgoing messages

4.0 AMQP Frame

Frames are divided into three distinct areas: a fixed width frame header, a variable width extended header, and a variable width frame body

frame header

The frame header is a fixed size (8 byte) structure that precedes each frame. The frame header includes mandatory information necessary to parse the rest of the frame including size and type information.

extended header

The extended header is a variable width area preceding the frame body. This is an extension point defined for future expansion. The treatment of this area depends on the frame type.

frame body

The frame body is a variable width sequence of bytes the format of which depends on the frame type.

amqp_18

amqp_19

5.0 Performative

amqp_15

 

 

6.0 QOS

AMQP provides QOS using disposition and transfer performative. Three QOS types are supported which is very similar to MQTT

  1. At-most once

If the sending application settles the delivery before sending it, this results in an at-most-once guarantee. The sender has indicated up front with his initial transmission that he has forgotten everything about this delivery and will therefore make no further attempts to send it. This is fire and forget approach.

If this delivery makes it to the receiver, the receiver clearly has no obligation to respond with updates of the receiver’s delivery state, as they would be meaningless and ignored by the sender

If message lost it will not be delivered again. It is a best effort service

amqp_12

2. At-least once

Sender sends delivery with settled = false and only settle when settled = true disposition received from the receiver. Sender sends transfer message and keeps it’s delivery tag in unsettled map

Receiver settle immediately upon processing the message. Receiver sends disposition with settled = true

Sender receives disposition with settled = true and removes delivery tag from unsettled map. At this point the delivery is considered settled and forgotten.

If transfer message is lost, sender will send the message again after ttl expiry

If the disposition frame is lost, sender will send the message again after ttl expiry

Receiver can get duplicate message. It is a two step process

amqp_13

3. Exactly once

In this method receiver receives message only once, duplicate message at receiver is not allowed, AMQP achieve this by having acknowledge from both Sender and Receiver. Sender sends transfer message with settled = false and keeps delivery tag in unsettled map

Receiver sends disposition with settled = false

sender sends disposition back with settled=true and remove delivery tag from unsettled map.

Upon receiving the settled = true from sender receiver also settled and removed tag from unsettled map

If transfer message is lost, sender will send the message again after ttl expiry

If the disposition frame from receiver  is lost, it will send is again after ttl expiry. If receiver doesn’t receive settled = true for delivery it will send disposition with settled = false again

Receiver will get only one message.

amqp_14

 

7.0 Message Transfer

Messaging frame follow AMQP transfer frame.

An annotated message consists of the bare message plus sections for annotation at the head and tail of the bare message. There are two classes of annotations: annotations that travel with the message indefinitely, and annotations that are consumed by the next node

The bare message consists of three sections: standard properties, application-properties, and application-data (the body). Application-properties is where you can add your properties

The bare message is immutable within the AMQP network. That is, none of the sections can be changed by any node acting as an AMQP intermediary

amqp_20

Altogether a message consists of the following sections:

  • Zero or one header.
  • Zero or one delivery-annotations.
  • Zero or one message-annotations.
  • Zero or one properties.
  • Zero or one application-properties.
  • The body consists of one of the following three choices: one or more data sections, one or more amqp-sequence sections, or a single amqp-value section.
  • Zero or one footer.

Header

The header section carries standard delivery details about the transfer of a message through the AMQP network. If the header section is omitted the receiver MUST assume the appropriate default values (or the meaning implied by no value being set) for the fields within the header unless other target or node specific defaults have otherwise been set

Delivery Annotations

The delivery-annotations section is used for delivery-specific non-standard properties at the head of the message. Delivery annotations convey information from the sending peer to the receiving peer. If the recipient does not understand the annotation it cannot be acted upon and its effects (such as any implied propagation) cannot be acted upon. Annotations might be specific to one implementation, or common to multiple implementations

If the delivery-annotations section is omitted, it is equivalent to a delivery-annotations section containing an empty map of annotations

Message Annotations

The message-annotations section is used for properties of the message which are aimed at the infrastructure and SHOULD be propagated across every delivery step. Message annotations convey information about the message

Message-properties

Immutable properties of the message. The properties section is used for a defined set of standard properties of the message. The properties section is part of the bare message; therefore, if retransmitted by an intermediary, it MUST remain unaltered

Application-properties

The application-properties section is a part of the bare message used for structured application data. Intermediaries can use the data within this structure for the purposes of filtering or routing. The keys of this map are restricted to be of type string (which excludes the possibility of a null key) and the values are restricted to be of simple types only, that is, excluding map, list, and array types

amqp_21

 

8.0 Demo

For the demo we will use RabbitMQ as broker and Qpid Proton Python as client

  • Follow this link to install RabbitMQ. Note: I am using Ubuntu 16.04 LTS

https://tecadmin.net/install-rabbitmq-server-on-ubuntu/

  • Enable AMQP 1.0 plugin
$sudo rabbitmq-plugins list
$sudo rabbitmq-plugins enable rabbitmq_amqp1_0
  • Check rabbitmq server status
$sudo service rabbitmq-server status
  • Install Python Qpid Proton package
$sudo apt-get update
$sudo apt-get install python-qpid-proton

 

Demo setup

Demo is setup on Ubuntu 16.04 VM.

amqp_11

Two programs created using Qpid Proton library, 1) producer 2) consumer. Producer will send  100 messages to RabbitMQ broker and consumer will retrieve messages from broker.  The destination queue in broker is /examples

Launch web browser with localhost:15672 to open rabbitMQ webUI , login/password: guest/guest, click on Queues and example queue. Here you can see message count increase when broker receive messages from producer

Below Producer and Consumer programs

producer.py. This program will send 100 messages with application properties and wait for confirmation from broker

from __future__ import print_function
import optparse
from proton import Message
from proton.handlers import MessagingHandler
from proton.reactor import Container

class Send(MessagingHandler):
    def __init__(self, url, messages):
        super(Send, self).__init__()
        self.url = url
        self.sent = 0
        self.confirmed = 0
        self.total = messages

    def on_start(self, event):
        event.container.create_sender(self.url)

    def on_sendable(self, event):
        while event.sender.credit and self.sent < self.total:
            msg = Message(id=(self.sent+1), body={'sequence':(self.sent+1)}, properties ={'Divine': 'Life'})
            event.sender.send(msg)
            self.sent += 1

    def on_accepted(self, event):
        self.confirmed += 1
        if self.confirmed == self.total:
            print("all messages confirmed")
            event.connection.close()

    def on_disconnected(self, event):
        self.sent = self.confirmed

parser = optparse.OptionParser(usage="usage: %prog [options]",
                               description="Send messages to the supplied address.")
parser.add_option("-a", "--address", default="localhost:5672/examples",
                  help="address to which messages are sent (default %default)")
parser.add_option("-m", "--messages", type="int", default=100,
                  help="number of messages to send (default %default)")
opts, args = parser.parse_args()

try:
    Container(Send(opts.address, opts.messages)).run()
except KeyboardInterrupt: pass

consumer.py. This program will retrieve messages from broker

from __future__ import print_function
import optparse
from proton.handlers import MessagingHandler
from proton.reactor import Container

class Recv(MessagingHandler):
    def __init__(self, url, count):
        super(Recv, self).__init__()
        self.url = url
        self.expected = count
        self.received = 0

    def on_start(self, event):
        event.container.create_receiver(self.url)

    def on_message(self, event):
        if event.message.id and event.message.id < self.received:
            # ignore duplicate message
            return
        if self.expected == 0 or self.received < self.expected:
            print(event.message.body)
            print(event.message.properties)
            self.received += 1
            if self.received == self.expected:
                event.receiver.close()
                event.connection.close()

parser = optparse.OptionParser(usage="usage: %prog [options]")
parser.add_option("-a", "--address", default="localhost:5672/examples",
                  help="address from which messages are received (default %default)")
parser.add_option("-m", "--messages", type="int", default=100,
                  help="number of messages to receive; 0 receives indefinitely (default %default)")
opts, args = parser.parse_args()

try:
    Container(Recv(opts.address, opts.messages)).run()
except KeyboardInterrupt: pass

Open a terminal and execute producer.py

$python producer.py

Open a browser and point to url localhost:15672. Click on queues and examples . You should see 100 messages in examples queue

amqp_24

Open another terminal and execute consumer.py program. You should see all 100 messages printed on screen and queue in rabbitmq server become zero

$python consumer.py

 

Lab-52: Constrained Application Protocol (CoAP)

1.0 Introduction

So far we have learned MQTT  in Lab-46 and AMQP protocol in Lab-53, its time to learn another IoT protocol called CoAP (Constrained Application Protocol).

CoAP is defined in RFC 7252. CoAP was developed for devices which are resource constrained and don’t have large memory or processing capability. The protocol is designed for machine to-machine (M2M) applications such as smart energy and building automation.

CoAP is very similar to HTTP protocol, it uses same methods as HTTP like GET, POST, PUT and DELETE. It uses same URI resource concept as HTTP and client server model. But that’s where similarity ends. Below are some points where CoAP differ from HTTP protocol

  • CoAP uses UDP as transport protocol instead of TCP in HTTP
  • Asynchronous message exchange, instead of synchronous message transfer in HTTP
  • Capability to work as pub sub protocol
  • CoAP URI starts with coap:// or coaps://

CoAP default port is 5683 and CoAP with DTLS default port is 5684

2.0 CoAP message header

CoAP uses fixed length binary header of 4 Bytes.  CoAP messages are encoded in a simple binary format. Each message contains a Message ID. Message ID used to detect duplicate packets  and for optional reliability. Reliability is provided by marking a message as Confirmable (CON). A Confirmable message is re-transmitted using a default timeout and exponential back-off between re-transmissions, until the recipient sends an Acknowledgement message (ACK) with the same Message ID

coap_5

Version (Ver)

2-bit unsigned integer. Indicates the CoAP version number. It MUST be set to 1 (01 binary). Other values are reserved for future versions. Messages with unknown version numbers MUST be silently ignored.

Type (T)

2-bit unsigned integer. Indicates if this message is of type Confirmable (0), Non-confirmable (1), Acknowledgement (2), or Reset (3)

Token Length (TKL)

4-bit unsigned integer. Indicates the length of the variable-length Token field (0-8 bytes). Lengths 9-15 are reserved, MUST NOT be sent, and MUST be processed as a message format error

Code

8-bit unsigned integer, split into a 3-bit class (most significant bits) and a 5-bit detail (least significant bits), documented as “c.dd” where “c” is a digit from 0 to 7 for the 3-bit subfield and “dd” are two digits from 00 to 31 for the 5-bit subfield. The class can indicate a request (0), a success response (2), a client error response (4), or a server error response (5). (All other class values are reserved).

As a special case, Code 0.00 indicates an Empty message

coap_9

coap_15

Response code definition

coap_17

coap_19

Message ID

16-bit unsigned integer in network byte order. Used to detect message duplication and to match messages of type Acknowledgement/Reset to messages of type Confirmable/Non confirmable

Token

The Token is used to match a response with a request. The token value is a sequence of 0 to 8 bytes. (Note that every message carries a token, even if it is of zero length.) Every request carries a client-generated token that the server MUST echo (without modification) in any resulting response

Options

Both requests and responses may include a list of one or more options. For example, the URI in a request is transported in several options, and metadata that would be carried in an HTTP header in HTTP is supplied as options as well

An Option is identified by an option number. Options can be Critical,  Unsafe, NoCacheKey and Repeatable

Critical:  An option that would need to be understood by the endpoint ultimately receiving the message in order to properly process the message

Unsafe: An option that would need to be understood by a proxy receiving the message in order to safely forward the message

NoCacheKey:

Repeatable: The definition of some options specifies that those options are repeatable. An option that is repeatable MAY be included one or more times in a message. An option that is not repeatable MUST NOT be included more than once in a message

coap_8

Below is the definition of commonly used Options

Uri-Host, Uri-Port, Uri-Path, and Uri-Query

The Uri-Host, Uri-Port, Uri-Path, and Uri-Query Options are used to specify the target resource of a request to a CoAP server.

  • the Uri-Host Option specifies the Internet host of the resource being requested. The default value of the Uri-Host Option is the IP literal representing the destination IP address of the request message
  • the Uri-Port Option specifies the transport-layer port number of the resource,
  • each Uri-Path Option specifies one segment of the absolute path to the resource, and
  • each Uri-Query Option specifies one argument parameterizing the resource

Example: Below example show how uri constructed from Options Uri-Path,Uir-Port & Uri-Host. Destination IP and port number are used from IP header and UDP header respectively

Input:
 Destination IP Address = [2001:db8::2:1] 
 Destination UDP Port = 5683 
 Uri-Host = "example.net" 
 Uri-Path = ".well-known" 
 Uri-Path = "core"

 Output:
 coap://example.net/.well-known/core

Content-format:

The Content-Format Option indicates the representation format of the message payload.

coap_16

Max-Age

The Max-Age Option indicates the maximum time a response may be cached before it is considered not fresh. It is used by the proxy server

Accept

The CoAP Accept option can be used to indicate which Content-Format is acceptable to the client. The representation format is given as a numeric Content-Format identifier that is defined in the “CoAP Content-Formats” registry. If no Accept option is given, the client does not express a preference (thus no default value is assumed)

3.0 CoAP URI

CoAP uses the “coap” and “coaps” URI schemes for identifying CoAP resources and providing a means of locating the resource. Resources are organized hierarchically and governed by a potential CoAP origin server listening for CoAP requests (“coap”) or DTLS-secured CoAP requests (“coaps”) on a given UDP port

coap-URI = “coap:” “//” host [ “:” port ] path-abempty [ “?” query ]

URI for secured CoAP. Default port for secured CoAP is 5684

coaps-URI = “coaps:” “//” host [ “:” port ] path-abempty [ “?” query ]

4.0 CoAP message types

Message types are defined by 2 bit type field in header. CoAP defines four messages (Confirmable, Non-confirmable, Reset, Acknowledgement ) and two responses (Piggybacked response and Separate response)

coap_18

Confirmable Message

Some messages require an acknowledgement. These messages are called “Confirmable”. When no packets are lost, each Confirmable message elicits exactly one return message of type Acknowledgement or type Reset.

If acknowledgement is not received a Confirmable message is re-transmitted using a default timeout and exponential back-off between re-transmissions, until the recipient sends an Acknowledgement message (ACK)

coap_1

Non-confirmable Message

Some messages do not require an acknowledgement. This is particularly true for messages that are repeated regularly for application requirements, such as repeated readings from a sensor

coap_2

Acknowledgement Message

An Acknowledgement message acknowledges that a specific Confirmable message arrived. By itself, an Acknowledgement message does not indicate success or failure of any request encapsulated in the Confirmable message, but the Acknowledgement message may also carry a Piggybacked Response.

Reset Message

A Reset message indicates that a specific message (Confirmable or Non-confirmable) was received, but some context is missing to properly process it. This condition is usually caused when the receiving node has rebooted and has forgotten some state that would be required to interpret the message. Provoking a Reset message (e.g., by sending an Empty Confirmable message) is also useful as an inexpensive check of the liveness of an endpoint (“CoAP ping”).

Piggybacked Response

A piggybacked Response is included right in a CoAP Acknowledgement (ACK) message that is sent to acknowledge receipt of the Request for this Response.

In this example response to client request of temperature is piggybacked into ACK message

coap_3

Separate Response

When a Confirmable message carrying a request is acknowledged with an Empty message (e.g., because the server doesn’t have the answer right away), a Separate Response is sent in a separate message exchange

In this example server can’t respond to client request right away so ACK is generated without response piggybacked. A separate response message is sent after some time. The message ID is different in separate response  but token is same as original request

coap_4

5.0 Resource Discovery

CoAP allows client to discover resource in server. This is accomplished by sending a GET request to server at well known URI-path (/.well-known/core). Upon receiving the GET request  server response with all resources it currently serving

In our example server we have two resources /temp & /humidity.  Client sends GET with Uri-path = /.well-known/core

coap_12

Server respond with resources /temp & /humidity

coap_13

6.0 Observing resources in CoAP

There is an extension to core CoAP protocol (RFC 7641) which provides CoAP subscribe and notification service. With this extension a CoAP client can subscribe to a resource in server. Whenever the state of resource changes server sends the notification to client.

Figure 2 below shows an example of a CoAP client registering its interest in a resource (/temperature). It sends a GET request with OBSERVE Option.  The server respond with current state of resource and add client to list of observer. After that server sends two notification upon changes to the resource state. The notification message from server contains same Token  id as GET. Both the registration request and the notifications are identified as such by the presence of the Observe Option

coap_6

Observe Option:

The Observe Option has the following properties. Its meaning depends on whether it is included in a GET request or in a response

When included in a GET request, the Observe Option extends the GET method so it does not only retrieve a current representation of the target resource, but also requests the server to add or remove an entry in the list of observers of the resource depending on the option value. The list entry consists of the client endpoint and the token specified by the client in the request. Possible Observer Option values are:
0 (register) – adds the entry to the list, if not present;
1 (deregister) – removes the entry from the list, if present.

When included in a response, the Observe Option identifies the message as a notification. This implies that a matching entry exists in the list of observers and that the server will notify the client of changes to the resource state

coap_7

Notifications are additional responses sent by the server in reply to the single extended GET request that created the registration. Each notification includes the token specified by the client in the request. The only difference between a notification and a normal response is the presence of the Observe Option.

A notification can be confirmable or non-confirmable, i.e., it can be sent in a confirmable or a non-confirmable message.

If the Observe Option in a GET request is set to 1 (deregister), then the server MUST remove any existing entry with a matching endpoint/ token pair from the list of observers and process the GET request as usual. The resulting response MUST NOT include an Observe Option.

Because messages can get reordered, the client needs a way to determine if a notification arrived later than a newer notification. For this purpose, the server MUST set the value of the Observe Option of each notification it sends to the 24 least significant bits of a strictly increasing sequence number. The sequence number MAY start at any value and MUST NOT increase so fast that it increases by more than 2^23 within less than 256 seconds

Below Wireshark capture of GET method with Observe Option. In this case client wants to subscribe to resource /temp

coap_14

Demo

We will use CoAP client and server python library from this link. I am using Ubuntu 16.04 on my Windows 10 Virtual box VM

Pre-requisite:

  • Ubuntu 16.04 VM
  • Start Wireshark and monitor packets on lookback interface. You can apply filter ‘coap’
  • Install coap python library
$sudo apt-get install pip
$pip install CoAPthon

We are adding two resources in the server, /temp and /humidity

self.add_resource('temp/', TempSensor())         
self.add_resource('humidity/', HumiditySensor())

Copy and paste below code in file coapserver.py. This will be our CoAP server

Copy and paste below code in file coapclient.py. This will simulate a CoAP client

#!/usr/bin/env python
 import getopt
 import socket
 import sys
 
 from coapthon.client.helperclient import HelperClient
 from coapthon.utils import parse_uri
 
 __author__ = 'Giacomo Tanganelli'
 
 client = None
 
 
 def usage():  # pragma: no cover
     print "Command:\tcoapclient.py -o -p [-P]"
     print "Options:"
     print "\t-o, --operation=\tGET|PUT|POST|DELETE|DISCOVER|OBSERVE"
     print "\t-p, --path=\t\t\tPath of the request"
     print "\t-P, --payload=\t\tPayload of the request"
     print "\t-f, --payload-file=\t\tFile with payload of the request"
 
 
 def client_callback(response):
     print "Callback"
 
 def client_callback_observe(response):  # pragma: no cover
     global client
     print "Callback_observe"
     check = True
     while check:
         chosen = raw_input("Stop observing? [y/N]: ")
         if chosen != "" and not (chosen == "n" or chosen == "N" or chosen == "y" or chosen == "Y"):
             print "Unrecognized choose."
             continue
         elif chosen == "y" or chosen == "Y":
             while True:
                 rst = raw_input("Send RST message? [Y/n]: ")
                 if rst != "" and not (rst == "n" or rst == "N" or rst == "y" or rst == "Y"):
                     print "Unrecognized choose."
                     continue
                 elif rst == "" or rst == "y" or rst == "Y":
                     client.cancel_observing(response, True)
                 else:
                     client.cancel_observing(response, False)
                 check = False
                 break
         else:
             break
 
 
 def main():  # pragma: no cover
     global client
     op = None
     path = None
     payload = None
     try:
         opts, args = getopt.getopt(sys.argv[1:], "ho:p:P:f:", ["help", "operation=", "path=", "payload=",
                                                                "payload_file="])
     except getopt.GetoptError as err:
         # print help information and exit:
         print str(err)  # will print something like "option -a not recognized"
         usage()
         sys.exit(2)
     for o, a in opts:
         if o in ("-o", "--operation"):
             op = a
         elif o in ("-p", "--path"):
             path = a
         elif o in ("-P", "--payload"):
             payload = a
         elif o in ("-f", "--payload-file"):
             with open(a, 'r') as f:
                 payload = f.read()
         elif o in ("-h", "--help"):
             usage()
             sys.exit()
         else:
             usage()
             sys.exit(2)
 
     if op is None:
         print "Operation must be specified"
         usage()
         sys.exit(2)
 
     if path is None:
         print "Path must be specified"
         usage()
         sys.exit(2)
 
     if not path.startswith("coap://"):
         print "Path must be conform to coap://host[:port]/path"
         usage()
         sys.exit(2)
 
     host, port, path = parse_uri(path)
     try:
         tmp = socket.gethostbyname(host)
         host = tmp
     except socket.gaierror:
         pass
     client = HelperClient(server=(host, port))
     if op == "GET":
         if path is None:
             print "Path cannot be empty for a GET request"
             usage()
             sys.exit(2)
         response = client.get(path)
         print response.pretty_print()
         client.stop()
     elif op == "OBSERVE":
         if path is None:
             print "Path cannot be empty for a GET request"
             usage()
             sys.exit(2)
         client.observe(path, client_callback_observe)
         
     elif op == "DELETE":
         if path is None:
             print "Path cannot be empty for a DELETE request"
             usage()
             sys.exit(2)
         response = client.delete(path)
         print response.pretty_print()
         client.stop()
     elif op == "POST":
         if path is None:
             print "Path cannot be empty for a POST request"
             usage()
             sys.exit(2)
         if payload is None:
             print "Payload cannot be empty for a POST request"
             usage()
             sys.exit(2)
         response = client.post(path, payload)
         print response.pretty_print()
         client.stop()
     elif op == "PUT":
         if path is None:
             print "Path cannot be empty for a PUT request"
             usage()
             sys.exit(2)
         if payload is None:
             print "Payload cannot be empty for a PUT request"
             usage()
             sys.exit(2)
         response = client.put(path, payload)
         print response.pretty_print()
         client.stop()
     elif op == "DISCOVER":
         response = client.discover()
         print response.pretty_print()
         client.stop()
     else:
         print "Operation not recognized"
         usage()
         sys.exit(2)
 
 
 if __name__ == '__main__':  # pragma: no cover
     main()
 

 

In one terminal execute coapserver.py script

$python ./coapserver.py

and in another terminal execute coapclient.py.  Below command will GET resource /temp from server. As can be seen server piggybacked payload {Temp: 60} with Ack frame

./coapclient.py -o GET -p coap://127.0.0.1:5683/temp
Source: ('127.0.0.1', 5683)
 Destination: None
 Type: ACK
 MID: 29531
 Code: CONTENT
 Token: None
 Payload: 
 {Temp: 60}

Below command for OBSERVE option. Here we are subscribing to resource /temp

./coapclient.py -o OBSERVE -p coap://127.0.0.1:5683/temp

To discover resources on server. Try below command. As can be seen in response server returns two resources /temp and /humidity

./coapclient.py -o DISCOVER -p coap://127.0.0.1:5683/
Source: ('127.0.0.1', 5683)
 Destination: None
 Type: ACK
 MID: 41102
 Code: CONTENT
 Token: None
 Content-Type: 40
 Payload: 
 </temp>;obs,</humidity>;obs,

Below example of POST

divine@divine-VirtualBox:~/coap$ ./coapclient.py -o POST -p coap://127.0.0.1:5683/temp -P {temp:100}
 Source: ('127.0.0.1', 5683)
 Destination: None
 Type: ACK
 MID: 53281
 Code: CREATED
 Token: dn
 Location-Path: temp
 Payload: 
 None
 
 divine@divine-VirtualBox:~/coap$ ./coapclient.py -o GET -p coap://127.0.0.1:5683/temp
 Source: ('127.0.0.1', 5683)
 Destination: None
 Type: ACK
 MID: 60262
 Code: CONTENT
 Token: None
 Payload: 
 {temp:100}

Below example of DELETE

divine@divine-VirtualBox:~/coap$ ./coapclient.py -o DELETE -p coap://127.0.0.1:5683/temp
 Source: ('127.0.0.1', 5683)
 Destination: None
 Type: ACK
 MID: 39338
 Code: DELETE
 Token: None
 Payload: 
 None
 
 divine@divine-VirtualBox:~/coap$ ./coapclient.py -o GET -p coap://127.0.0.1:5683/temp
 Source: ('127.0.0.1', 5683)
 Destination: None
 Type: ACK
 MID: 55286
 Code: NOT_FOUND
 Token: None
 Payload: 
 None