Use CoAP and the Observer design pattern to work with IoT devices.
The first article in this series on the Constrained Application Protocol (CoAP) covered the basics and explored how to add CoAP messaging in your own Java applications. This article concludes the discussion by exploring advanced topics such as the Observer design pattern, device discovery, and cross-protocol proxies.
CoAP meets the Observer pattern
CoAP, a basic REST-like request/response protocol, facilitates extensions. One example is the CoAP extension for observing resources via the Observer pattern.
To register interest in updates to a resource without polling for it continually, the CoAP client simply adds an OBSERVE entry in the request header with a value of 0. The Observer extension to CoAP supports confirmable and nonconfirmable registrations for resource updates. The sequence diagram in Figure 1 is a sample confirmable (CON) observer registration request with updates.
Figure 1. A CON request with observer registration
First, client 1 makes a request to client 2 with the OBSERVE option in the header set to 0. This indicates a registration request. Client 2 sends an update with current data, echoing the token client 1 sent with the request, and it continues to use this token with each update. Since the request was a CON message, each response with data requires an ACK.
As time passes and the observed value changes, client 2 sends the new value in an updated CON response. Again, these updates contain the same token as the initial request, and client 1 must send an ACK. If an ACK is not sent, client 2 deregisters client 1 after the timeout period.
With the Californium CoAP library on the server side, the observation is implemented by marking the resource as observable, as shown in Listing 1.
Listing 1. Marking a CoAP server resource as observable
public class CoapObserveServer extends CoapResource {
// ...
public CoapObserveServer(String name) {
super(name);
// enable observations and set type to CONS
setObservable(true);
setObserveType(Type.CON);
// mark observable in the Link-Format
getAttributes().setObservable();
// schedule a periodic update timer
// alternatively, call changed() as needed
new Timer().schedule(new UpdateTask(), 0, 1000);
}
For a client application, setting up the Observer pattern is like an asynchronous resource request (see Listing 2). You can download the complete client application from my GitHub repository.
Listing 2. Implementing the CoAP Observer pattern
class AsynchListener implements CoapHandler {
@Override
public void onLoad(CoapResponse response) {
System.out.println( response.getResponseText() );
}
@Override
public void onError() { /*...*/ }
}
//...
CoapClient client =
new CoapClient("coap://10.0.1.97:5683/temp");
// observer pattern uses asynchronous listener
AsynchListener asynchListener =
new AsynchListener();
CoapObserveRelation observation =
client.observe(asynchListener);
// ...
observation.proactiveCancel();
As with an asynchronous GET request, the first step is to supply a callback in the call to CoapClient.observe(). From that point onward, the callback receives updates as the data changes (such as measured temperature changes), according to the server resource.
On the server, calling the CoapResource.changed() method causes this CoAP server to automatically send a subsequent response (a temperature update) to the initial GET request for data on the observable resource, as shown in Listing 3.
Listing 3. Periodic updates cause a GET response to be sent to all observers.
private class UpdateTask extends TimerTask {
@Override
public void run() {
changed(); // notify all observers
}
}
@Override
public void handleGET(CoapExchange exchange) {
// the Max-Age value should match the update interval
exchange.setMaxAge(1);
exchange.respond("Current temperature: " +
getCurrentTemp() );
}
For each active observer, the onload method is called, and the latest data value (temperature, in this example) is sent in the response. As shown at the end of Listing 2, you can cancel the observation and stop the updates by calling CoapObserveRelation.proactiveCancel(). This method sends a RESET message to the server in response to the next update. The server then removes this client from the list of observers for the associated resource.
Device discovery using CoAP
CoAP supports dynamic device discovery, which is useful in an Internet of Things (IoT) environment of changing networks of devices and sensors. To discover another CoAP server, the client is required to either know about the resource ahead of time or to support multicast CoAP via User Datagram Protocol (UDP) messaging on a multicast address and port. Servers that wish to be discoverable must listen and reply to requests on the “all CoAP nodes” multicast address to let other clients or servers know of its existence and addressable URI.
The multicast “all CoAP nodes” address is 224.0.1.187 for IPv4 and FF05::FD for IPv6. Sending a request for the CoAP resource directory name /.well-known/core should result in a reply from every reachable CoAP resource on the local network segment listening on the multicast address (see Listing 4).
Listing 4. Listening for “all CoAP nodes” multicast requests
CoapServer server = ...
InetAddress addr = InetAddress.getByName("224.0.1.187");
bindToAddress = new InetSocketAddress(addr, COAP_PORT);
CoapEndpoint multicast =
CoapEndpoint.builder()
.setInetSocketAddress(bindToAddress)
.setPort(5683)
.build();
server.addEndpoint(multicast);
In Listing 4, the multicast address is set as a CoapEndpoint to the Californium CoapServer object. You can create a CoAP GET request to discover CoAP servers and their resources, as shown in Listing 5, this time using the IPv6 multicast address.
Listing 5. A CoAP GET request to discover CoAP servers and resources on a local network
CoapClient client =
new CoapClient("coap://FF05::FD:5683/.well-known/core");
client.useNONs();
CoapResponse response = client.get();
if ( response != null ) {
// get server's IP address
InetSocketAddress addr =
response.advanced()
.getSourceContext()
.getPeerAddress();
int port = addr.getPort();
System.out.println("Source address: " +
addr + ":" + port);
}
Note that the request must be a NON GET request, hence the call to client.useNONs() in the second line. Additionally, making a request to the base URI coap://FF05::FD:5683 yields basic information about the server, such as the resources and associated URIs it supports.
Dynamic resource discovery is useful when you’re building a dynamic IoT application; in that case, it’s not desired to hardcode or manually configure available CoAP servers and their resources.
For instance, if you have a single controller application that allows all lights (or other appliances) within a room or building floor to be turned off and on together, you can use a resource discovery to locate all available smart lighting devices. Using the results of the discovery, you can send CoAP commands to each smart lighting device to turn off and on, as appropriate. If new lighting devices are added at some future date, the controller code continues to work on all lighting devices with no change needed.
The CoAP resource directory
To better enable resource discovery for constrained devices — that is, some devices that are sleeping or noncommunicative at times — a CoAP resource directory was defined. This entity maintains descriptions of CoAP servers and resources within your distributed application. Specifically, devices can register themselves as servers, along with their resources, in a well-known resource directory node.
CoAP client applications can subsequently refer to the resource directory to learn about resources as they become available and then become part of the distributed application.
CoAP endpoints register themselves with the resource directory via its registration interface (see Figure 2). CoAP client applications then use the lookup or CoAP group interfaces (more on groups in the next sections) to learn about available resources.
Figure 2. The CoAP resource directory interfaces
Endpoints register their resources by sending a POST request with the resource path (such as /temp), the endpoint name, and optional data such as a domain, the endpoint type, and other data. If the POST request is successful, the response code is 2.01, and a resource identifier is returned (such as /rd/1234). This identifier can be used to access the CoAP resource through the resource directory server. For example, the code in Listing 6 registers a pulse oximeter’s heart rate and oxygen saturation telemetry resources.
Listing 6. The client code to register CoAP endpoint resources with a resource directory server
String host = "coap://10.0.1.111/";
CoapClient rd;
System.out.println("Registering resource: heart rate ");
rd = new CoapClient(host+"rd?ep=pulseoximeter/heartrate/");
resp = rd.post("</pulseoximeter/heartrate>;"
+ "ct=41;rt=\"Heartrate Resource\";"
+ "if=\"sensor\"",
MediaTypeRegistry.APPLICATION_LINK_FORMAT);
System.out.println("--Response: " +
resp.getCode() + ", " +
resp.getOptions().getLocationString());
System.out.println("Registering resource: oxygen-saturation ");
rd = new CoapClient(host+"rd?ep=pulseoximeter/oxygen-saturation/");
resp = rd.post("</pulseoximeter/oxygen-saturation>;"
+ "ct=41;rt=\"Oxygen Saturation Resource\";"
+ "if=\"sensor\"",
MediaTypeRegistry.APPLICATION_LINK_FORMAT);
System.out.println("--Response: " +
resp.getCode() + ", " +
resp.getOptions().getLocationString());
First, the code connects to a CoAP resource directory server running on node 10.0.1.111 (an arbitrary address for this example). It connects using a resource endpoint name of /pulseoximeter/heartrate because that’s the resource it registers first. Next, a POST request is made using that endpoint, a URI path of /pulseoximeter/heartrate, a name of Heartrate Resource, and an endpoint type sensor. The same is done for the other resource, /pulseoximeter/oxygen-saturation. When this is executed successfully, you should see output like Listing 7.
Listing 7. The result of a successful resource registration
Registering resource: heartrate
--Response: 2.01, /rd/pulseoximeter/heartrate
Registering resource: oxygen-saturation
--Response: 2.01, /rd/pulseoximeter/oxygen-saturation
To further illustrate the results of registration, Listing 8 adds code to make a resource discovery request to the resource directory server.
Listing 8. Sending a resource discovery request to the resource directory server
CoapClient q = new CoapClient("coap://10.0.1.111/.well-known/core");
CoapResponse resp = q.get();
System.out.println( "--Registered resources: " +
resp.getResponseText());
Adding this code both before the endpoint registration requests and after yields the complete set of output shown in Listing 9.
Listing 9. The results of endpoint registration
--Registered resources: </rd>;rt="core.rd",</rd-lookup>;rt="core.rd-lookup",</rd-lookup/d>,</rd-lookup/ep>,</rd-lookup/res>,</.well-known/core>,</tags>
Registering resource: heartrate
--Response: 2.01, /rd/pulseoximeter/heartrate
Registering resource: oxygen-saturation
--Response: 2.01, /rd/pulseoximeter/oxygen-saturation
--Registered resources: </rd>;rt="core.rd",</rd/pulseoximeter/heartrate/>,</rd/pulseoximeter/oxygen-saturation/>,</rd-lookup>;rt="core.rd-lookup",</rd-lookup/d>,</rd-lookup/ep>,</rd-lookup/res>,</.well-known/core>,</tags>
Note that the result from the resource discovery request, made after the endpoint registration POST requests, now includes the two added pulse oximeter endpoint resources, as expected.
CoAP resource group definitions
To further enable CoAP device group communication, the CoAP CoRE Working Group defined the “Group Communication for the Constrained Application Protocol” specification (RFC 7390). (CoRE stands for constrained RESTful environments.) This specification outlines methods to define and subsequently communicate with groups of devices.
For instance, for a CoAP server that supports RFC 7390, a POST request to the resource /coap-group with a group name and index provided as form data would create a new CoAP resource group. An example is shown in Listing 10.
Listing 10. CoAP resource group creation as a POST message
POST /coap-group
Content-Format: application/coap-group+json
{
"n": "lights.floor1.example.com",
"a": "[ff15::4200:f7fe:ed37:abcd]:1234"
}
If the action is successful, the response is like that shown in Listing 11.
Listing 11. The response for a POST request to create a new CoAP resource group
2.01 Created
Location-Path: /coap-group/12
Sending a GET request to /coap-group returns JSON data indicating all members of the group. A sample response is shown in Listing 12.
Listing 12. A CoAP GET response for a CoAP group request
2.05 Content
Content-Format: application/coap-group+json
{
"8" : { "a": "[ff15::4200:f7fe:ed37:14ca]" },
"11": { "n": "sensors.floor1.example.com",
"a": "[ff15::4200:f7fe:ed37:25cb]" },
"12": { "n": "lights.floor1.example.com",
"a": "[ff15::4200:f7fe:ed37:abcd]:1234" }
}
Subsequently, you can make requests to a specific group, such as /coap-group/12, to control or read the status of the devices within that group as a whole.
Cross-protocol proxying (CoAP and HTTP)
Because CoAP is a REST-based protocol based on the underlying HTTP implementation, it’s straightforward to map CoAP methods to HTTP. As such, it’s straightforward to proxy CoAP requests to and from HTTP so CoAP clients can access resources available on an HTTP server or allow HTTP clients (such as JavaScript code) to make requests to CoAP servers.
For example, the code in Listing 13 is from the Californium sample code and shows how to implement a simple CoAP-to-CoAP and CoAP-to-HTTP proxy.
Listing 13. A simple CoAP-to-HTTP proxy from the Californium sample applications
private static class TargetResource extends CoapResource {
private int counter = 0;
public TargetResource(String name) {
super(name);
}
@Override
public void handleGET(CoapExchange exchange) {
exchange.respond(
"Response " + (++counter) +
" from resource " + getName() );
}
}
public coap_cross_proxy() throws IOException {
ForwardingResource coap2coap =
new ProxyCoapClientResource("coap2coap");
ForwardingResource coap2http =
new ProxyHttpClientResource("coap2http");
// Create CoAP Server with proxy resources
// from CoAP to CoAP and HTTP
targetServerA = new CoapServer(8082);
targetServerA.add(coap2coap);
targetServerA.add(coap2http);
targetServerA.start();
ProxyHttpServer httpServer =
new ProxyHttpServer(8080);
httpServer.setProxyCoapResolver(
new DirectProxyCoapResolver(coap2coap) );
System.out.println(
"CoAP resource \"target\" available over HTTP at: " +
"http://localhost:8080/proxy/coap://localhost:PORT/target");
}
Running a resource named helloWorld as coap://localhost:5683/helloWorld and browsing to the proxy URL, as specified in the code, results in the payload being displayed in the browser; see Figure 3.
Figure 3. The result of a CoAP-to-HTTP proxy, with the response text displayed in the browser
By the way, CoAP can be proxied to other protocols, such as the Session Initiation Protocol (SIP) and the Extensible Messaging and Presence Protocol (XMPP).
The Californium CoAP implementation and cf-browser
The Eclipse Californium project provides an open source CoAP implementation you can use to enable CoAP communication in your applications. The project can be downloaded from the GitHub repository.
You need Maven to build and install Californium. To use Maven, set your JAVA_HOME and M2_HOME environment variables, and then run the following Maven command:
> mvn clean install -DskipTests
You can include Californium in your projects by adding the following to your Maven pom.xml file:
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId>
<version>3.5.0</version>
</dependency>
Californium comes with a set of tools to help you code and debug CoAP applications. One of the most useful is a JavaFX-based browser, cf-browser, that you can use to visually discover, explore, and interact with CoAP devices on your network. It’s a useful tool that I recommend for learning and debugging CoAP endpoint programming.
First, clone the GitHub repository:
> git clone https://github.com/eclipse/californium.tools.git
Next, install OpenJFX. For Windows, download the binary from OpenJFX. For Linux, install it with Aptitude using the following command:
> sudo apt install openjfx
Next, within the californium.tools directory, build the tools with the following command:
> mvn clean install
To run the CoAP browser change into the cf-browser directory, and run the following command:
> mv javafx:run
Once cf-browser is installed and running, browse to the address of a CoAP node on your network. For instance, Figure 4 shows the result of typing coap://localhost:5683 into the Target address textbox and then clicking the DISCOVERY button in the upper right.
Figure 4. The cf-browser tool visually displays and interacts with CoAP instances.
You can interact with the tool by discovering the resources available on your network and sending data to resources you select via the buttons labeled GET, POST, and so on. You can even receive continuous CoAP updates for observable resources by selecting the OBSERVE button. The resulting data is displayed in the Response section, as shown in Figure 5.
Figure 5. Interacting with CoAP resources to discover and debug them with cf-browser
Other tools included with Californium are a command-line client (cf-client) you can use to easily listen on CoAP resources and a standalone CoAP server.
Source: oracle.com
0 comments:
Post a Comment