SDK - The low-level CSE Client

Introduction

The OpenMTC SDK offers a client module for low-level access to the oneM2M resource tree exposed by a CSE's reference point. Currently, only the http and https protocols are supported.

Basically, there are different types of Common Service Entities (CSE):

  • MN-CSE: Middle Node CSE (OpenMTC Gateway)
  • IN-CSE: Infrastructure Node CSE (OpenMTC Backend)

The client module comprises classes for representing requests, responses as well as classes that provide an abstraction for a connection to a CSE's reference point (the actual client itself).

Requests

Requests to a CSE are called a OneM2MRequest. The OpenMTC SDK provides this class for representing the different types of requests that can be issued towards a CSE. This class resides under the openmtc_onem2m.transport package. The following requests (OneM2MOperation) are available:

  • retrieve
  • delete
  • create
  • notify
  • update

OneM2MRequest - Retrieve

The most trivial case of a OneM2MRequest is the retrieve. It takes the path of the resource to be retrieved as parameter upon construction.

This file can be found here.

# Example 3: Retrieve OneM2MRequest

from openmtc_onem2m.transport import OneM2MRequest

request = OneM2MRequest("retrieve", to="onem2m")

print request.to
#>>> onem2m

OneM2MRequest - Delete

Like the retrieve OneM2MRequest, a delete OneM2MRequest merely takes the path of the resource to be deleted as parameter upon construction.

This file can be found here.

# Example 4: Delete OneM2MRequest

from openmtc_onem2m.transport import OneM2MRequest

request = OneM2MRequest("delete", to="onem2m")

print request.to
#>>> onem2m

OneM2MRequest - Create

When creating a create OneM2MRequest object we need to specify the object to be created together with the path where it is to be created. In most cases this is done by creating an appropriate resource object and passing it.

This file can be found here.

# Example 5a: Create OneM2MRequest

from openmtc_onem2m.transport import OneM2MRequest
from openmtc_onem2m.model import AE

my_app = AE(App_ID="myApp")

request = OneM2MRequest("create", to="onem2m", pc="my_app")

print request.to
#>>> onem2m
print request.pc
#>>> myApp

When creating contentInstances, we can also pass in a string of raw data. In this case, we also need to specify the mime-type of the data via the resource_type parameter (ty).

This file can be found here.

# Example 5b: Create OneM2MRequest with data

from openmtc_onem2m.transport import OneM2MRequest
import json

sensor_data = {"type": "temperature",
               "value": 15 }

data_string = json.dumps(sensor_data)

request = OneM2MRequest("create", 
                        to="onem2m",
                        pc=data_string,
                        ty="application/json")

print request.to
#>>> onem2m
print request.pc
#>>> {"type": "temperature", "value": 15}

OneM2MRequest - Notify

For notify OneM2MRequest objects the same semantics as for create OneM2MRequest apply.

This file can be found here.

# Example 6a: Notify OneM2MRequest

from openmtc_onem2m.transport import OneM2MRequest
from openmtc_onem2m.model import AE

my_app = AE(App_ID="myApp")

request = OneM2MRequest("notify", to="onem2m", pc=my_app)

print request.to
#>>> onem2m
print request.pc.App_ID
#>>> myApp

This file can be found here.

# Example 6b: Notify OneM2MRequest with data

from openmtc_onem2m.transport import OneM2MRequest
import json

sensor_data = {"type": "temperature",
               "value": 15 }

data_string = json.dumps(sensor_data)

request = OneM2MRequest("create", 
                        to="onem2m",
                        pc=data_string,
                        ty="application/json")

print request.to
#>>> onem2m
print request.pc
#>>> {"type": "temperature", "value": 15}

OneM2MRequest - Update

The update OneM2MRequest can be used to update specific attributes of an object (AE). If the request is legal, four different cases are distinguished:

  • If an attribute value is provided in the OneM2MRequest that exists in the target resource, the CSE will simply update that attribute in the resource representation.
  • If an attribute is not provided in the OneM2MRequest, but the attribute exists in the target resource, the hosting CSE will simply leave the value of that attribute unchanged.
  • If an attribute is provided in the OneM2MRequest and does not exist in the target resource, the hosting CSE will create such attribute with the provided value.
  • If an attribute is set to NULL in the OneM2MRequest and exists in the target resource, the hosting CSE will delete such attribute if the deletion of the attribute is allowed by the local policy.

The following example shows the creation of a update OneM2MRequest. The CSE would either update the attribute (labels) in the resource representation if it exists already exists there, or create the attribute labels with the provided value if it does not exist in the CSE resource representation yet.

This file can be found here.

# Example 7: Update OneM2MRequest

from openmtc_onem2m.transport import OneM2MRequest
from openmtc_onem2m.model import AE

my_app = AE(App_ID="myApp", labels=["keyword1", "keyword2"])

request = OneM2MRequest("update", to="onem2m", pc=my_app.labels)

print request.to
#>>> onem2m
print request.pc
#>>> [u'keyword1', u'keyword2']

Responses

Upon servicing a request, a CSE will return a OneM2MResponse, which is a class of the client module. This class is defined in the openmtc_onem2m.transport module and derives from the object base class. The following response types are possible:

  • Create
  • Retrieve
  • Update
  • Delete
  • Notify
  • Execute
  • Observe

An OneM2MResponse has the following properties:

  • status_code - Denotes the result status of the operation (see below).
  • request - The type of the operation. One of (create, retrieve, update, delete, notify, execute, observe).
  • rqi - Denotes the request identifier (requestIdentifier).
  • pc - Denotes the resource content (primitiveContent).
  • to - Denotes to destination of the response.

Error Responses

If an error occurs on the CSE servicing the request, the CSE will return a OneM2MErrorResponse. Note that the OneM2MErrorResponse class is an Exception. In case that any error is reported by the CSE during processing a request, the client will raise an instance of OneM2MErrorResponse. The OneM2MErrorResponse heritates from the classes OneM2MResponse and OneM2MError (OpenMTCError) and is not yet implemented (pass).

Status Codes

The status_code of OneM2MResponse objects are defined as constants in the openmtc_onem2m.exc module. The following constants are defined:

STATUS numeric_code http_status_code
STATUS_ACCEPTED 1000 202
STATUS_OK 2000 200
STATUS_CREATED 2001 201
STATUS_BAD_REQUEST 4000 400
STATUS_NOT_FOUND 4004 404
STATUS_OPERATION_NOT_ALLOWED 4005 405
STATUS_REQUEST_TIMEOUT 4008 408
STATUS_SUBSCRIPTION_CREATOR_HAS_NO_PRIVILEGE 4101 403
STATUS_CONTENTS_UNACCEPTABLE 4102 400
STATUS_ACCESS_DENIED 4103 403
STATUS_GROUP_REQUEST_IDENTIFIER_EXISTS 4104 409
STATUS_CONFLICT 4015 409
STATUS_INTERNAL_SERVER_ERROR 5000 500
STATUS_NOT_IMPLEMENTED 5001 501
STATUS_TARGET_NOT_REACHABLE 5103 404
STATUS_NO_PRIVILEGE 5105 403
STATUS_ALREADY_EXISTS 5106 403
STATUS_TARGET_NOT_SUBSCRIBABLE 5203 403
STATUS_SUBSCRIPTION_VERIFICATION_INITIATION_FAILED 5204 500
STATUS_SUBSCRIPTION_HOST_HAS_NO_PRIVILEGE 5205 403
STATUS_NON_BLOCKING_REQUEST_NOT_SUPPORTED 5206 501
STATUS_EXTERNAL_OBJECT_NOT_REACHABLE 6003 404
STATUS_EXTERNAL_OBJECT_NOT_FOUND 6005 404
STATUS_MAX_NUMBER_OF_MEMBER_EXCEEDED 6010 400
STATUS_MEMBER_TYPE_INCONSISTENT 6011 400
STATUS_MANAGEMENT_SESSION_CANNOT_BE_ESTABLISHED 6020 500
STATUS_MANAGEMENT_SESSION_ESTABLISHMENT_TIMEOUT 6021 500
STATUS_INVALID_CMDTYPE 6022 400
STATUS_INVALID_ARGUMENTS 6023 400
STATUS_INSUFFICIENT_ARGUMENT 6024 400
STATUS_MGMT_CONVERSION_ERROR 6025 500
STATUS_CANCELLATION_FAILED 6026 500
STATUS_ALREADY_COMPLETE 6028 400
STATUS_COMMAND_NOT_CANCELLABLE 6029 400

Exceptions

In addition to raising an instance of OneM2MErrorResponse, the CSE client might also inidcate error conditions that do not occur while the CSE was processing the request. This will mainly happen when the client was unable to contact the CSE for whatever reason.

Exeptions that are raised will be subclasses of the OpenMTCError class defined in the openmtc.exc module.

Using the Client

The client implementation for interfacing with the HTTP interface of an CSE resides in the openmtc_onem2m.client.http module. The implementing class is called OneM2MHTTPClient. In the current version of the SDK, we simply import the class directly. This is planned to be replaced with a more sophisticated factory pattern that creates appropriate clients based on the transport scheme (e.g. http or mqtt) that is used.

Client objects expose a method called send_onem2m_request for sending OneM2MRequest objects to a CSE.

Creating a Client

To create a client object, we simply import the OneM2MHTTPClient class from the openmtc_onem2m.client.http module and create an instance of it with the URI of a reference point of an oneM2M CSE.

This file can be found here.

# Example 8a: Creating a Client

from openmtc_onem2m.client.http import OneM2MHTTPClient

# create a OneM2MHTTPClient object
client = OneM2MHTTPClient("http://localhost:8000", False)

Making Requests

To retrieve a resource from the CSE's resource tree, we can use the send_onem2m_request method and pass an appropriate OneM2MRequest object. In this case we retrieve the CSEBase resource of the CSE's resource tree. If successful, the operation returns a promise, which contains an OneM2MResponse object. The OneM2MResponse can be obtained from the promise by using .get(). The content property of the OneM2MResponse holds the appropriate CSEBase object.

This file can be found here.

# Example 8b: Making Requests

from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest

# create a OneM2MHTTPClient object
client = OneM2MHTTPClient("http://localhost:8000", False)

# create a OneM2MRequest object
onem2m_request = OneM2MRequest("retrieve", to="onem2m")
# send the OneM2MRequest to the CSE
promise = client.send_onem2m_request(onem2m_request)
# reteive the OneM2MResponse from the returned promise
onem2m_response = promise.get()

print onem2m_response.to
#>>> onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> CSEBase(path='None', id='cb0')

Note: This example (and most of the following ones) will only work as shown, if a gateway instance is running in the background of the localhost. This can be launched by running the openmtc-open-source/openmtc-gevent/run_gateway script.

To create a resource on the CSE, we first create the desired resource object and then send a create OneM2MRequest.

In the following example, we will add the optional pararmeter resourceName="MYAPP" to the creation of the AE in order to facilitate the retrieval of this AE in the browser. After execution of the example (and the condition to have running CSE on the localhost) the created AE on the CSE should be retrievable at URL http://localhost:8000/onem2m/MYAPP in a browser on the localhost. Further, we add the mandatory parameter requestReachability=False which states, that the created AE should have no server capability and therefore no reachability for other instances.

For a create OneM2MRequest, there are two additional parameters: ty=AE indicates that the resource that should be created on the CSE is of type AE (ApplicationEntity). The statement pc=my_app specifies what resource should be created on the CSE. In this case, it is the AE created previously.

This file can be found here.

# Example 10: Create a resource

from openmtc_onem2m.model import AE
from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest

# create a OneM2MHTTPClient object
client = OneM2MHTTPClient("http://localhost:8000", False)

# create a resource to be created on the CSE
# resourceName: (optional) for easy check in browser
# requestReachability: (mandatory) for servercapability of the AE
my_app = AE(App_ID="myApp", 
            labels=["keyword1", "keyword2"], 
            resourceName="MYAPP", 
            requestReachability=False)

# create a OneM2MRequest object of type 'create'
# ty: resource_type of the created resource
# pc: Resource content to be transferred
onem2m_request = OneM2MRequest("create", to="onem2m", ty=AE, pc=my_app)

# send the 'create' OneM2MRequest to the CSE
promise = client.send_onem2m_request(onem2m_request)

# reteive the OneM2MResponse from the returned promise
onem2m_response = promise.get()

print onem2m_response.to
#>>> onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2001, description='CREATED', http_status_code=201)
print onem2m_response.content
#>>> AE(path='None', id='ae0')
print onem2m_response.content.App_ID
#>>> myApp
print onem2m_response.content.labels
#>>> [u'keyword1', u'keyword2']

Note: If this example throws a OneM2MErrorResponse with response_status_code: STATUS(numeric_code=4015, description='CONFLICT', http_status_code=409), then the resourceName might already be registered at the CSE. Try to alter the resourceName. ResourceNames need to be unique on the CSE. Alternatively, the running CSE process can be terminated and restarted. This avoids the need to change the resourceName.

Note: At this point the application object has been created in the CSE's resource tree. However, the original object we created in our program (my_application) has not been altered in any way. Specifically, it does not contain any attributes that may have been set or altered by the CSE, nor has its path property been set.

If we want to continue working with the application object it is good practice to retrieve the object again through the resourceName.

This file can be found here.

# Example 11a: Create a resource (continued)

from openmtc_onem2m.model import AE
from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest

client = OneM2MHTTPClient("http://localhost:8000", False)

my_app = AE(App_ID="myApp", 
            labels=["keyword1", "keyword2"], 
            resourceName="MYAPP1", 
            requestReachability=False)

onem2m_request = OneM2MRequest("create", to="onem2m", ty=AE, pc=my_app)

promise = client.send_onem2m_request(onem2m_request)

onem2m_response = promise.get()

print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2001, description='CREATED', http_status_code=201)

# Build path to retieve from
path = "onem2m/" + onem2m_response.content.resourceName
print path
#>>> onem2m/MYAPP

# Retrieve the AE from the CSE
onem2m_request = OneM2MRequest("retrieve", to=path)
promise = client.send_onem2m_request(onem2m_request)
onem2m_response = promise.get()

print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> AE(path='None', id='ae0')

# Set the local AE to the retrieved content
my_app = None
my_app = onem2m_response.content

print my_app.App_ID
#>>> myApp
print my_app.resourceName
#>>> MYAPP
print my_app.labels
#>>> [u'keyword1', u'keyword2']

Note: Again, if this example throws a OneM2MErrorResponse with response_status_code: STATUS(numeric_code=4015, description='CONFLICT', http_status_code=409), then the resourceName might already be registered at the CSE. Try to alter the resourceName. Alternatively, the running CSE process can be terminated and restarted. This avoids the need to change the resourceName.

The following example showcases how to update some fields using OneM2MRequest update.

This file can be found here.

# Example 11b: Updating a resource using OneM2MRequest Update

from openmtc_onem2m.model import AE
from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest

client = OneM2MHTTPClient("http://localhost:8000", False)

my_app = AE(App_ID="myApp", 
            labels=["keyword1", "keyword2"], 
            resourceName="MYAPP2", 
            requestReachability=False)

# Create the AE 'my_app' at the CSE
onem2m_request = OneM2MRequest("create", to="onem2m", ty=AE, pc=my_app)
promise = client.send_onem2m_request(onem2m_request)
onem2m_response = promise.get()
print onem2m_response.content.labels
#>>> [u'keyword1', u'keyword2']

# Retrieve the AE from the CSE and check the labels
path = "onem2m/" + onem2m_response.content.resourceName
onem2m_request = OneM2MRequest("retrieve", to=path)
promise = client.send_onem2m_request(onem2m_request)
onem2m_response = promise.get()
print onem2m_response.content.labels
#>>> [u'keyword1', u'keyword2']

# Update the changes labels in the remote resource
# Therefore a temporay AE object is needed
# This temporary AE object should ONLY contian the fields that need to be updated 
tmp_app = AE(labels=["foo", "bar", "coffee"])
onem2m_request = OneM2MRequest("update", to=path, pc=tmp_app)
promise = client.send_onem2m_request(onem2m_request)
onem2m_response = promise.get()
print onem2m_response.content.labels
#>>> [u'foo', u'bar', u'coffee']

# Set the local AE to the retrieved content
my_app = None
my_app = onem2m_response.content
print my_app.labels
#>>> [u'foo', u'bar', u'coffee']

Error Handling

The examples above have so far omitted error handling for the sake of clarity and brevity. Obviously however, many things can go wrong at various stages of processing and these cases need to be dealt with.

Any errors that are returned from the CSE will be represented in the form of an OneM2MErrorResponse instance. As stated before, the OneM2MErrorResponse class derives from Exception. Consequently, OneM2MErrorResponse objects are not returned from the method, instead they are raised as exceptions.

In addition, it is possible that the CSE could not be contacted at all in the first place. In this case, an instance of openmtc.exc.ConnectionFailed will be raised, which also derives from Exception.

Note: This implies that whenever one of the client methods returns normally, we can be sure that the operation has succeeded and continue working with the result as planned without further inspecting the result's status. This allows a very convenient and pythonic separation of error and result handling.

With this in mind we can extend Example 8b by simply enclosing the invocation of the client method in a try/except/else block.

This file can be found here.

# Example 12a: Making Requests with error handling

from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest, OneM2MErrorResponse
from openmtc.exc import OpenMTCError

client = OneM2MHTTPClient("http://localhost:8000", False)

try:
    onem2m_request = OneM2MRequest("retrieve", to="onem2m")
    promise = client.send_onem2m_request(onem2m_request)
    onem2m_response = promise.get()
except OneM2MErrorResponse as e:
    print "CSE reported an error:", e
    raise
except OpenMTCError as e:
    print "Failed to reach the CSE:", e
    raise
else:
    pass

# no exception was raised, the method returned normally.
print onem2m_response.to
#>>> onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> CSEBase(path='None', id='cb0')

Forwarding

OpenMTC will automatically handle forwarding of a OneM2MRequest if it is referring to a different CSE than the one the client is connected to. Forwarding in OneM2M is based on CSE-IDs whereas the ETSI M2M equivalent Retargeting is based on IPs.

Lets suppose that a gateway is availabe at localhost:8000 and has the CSE-ID mn-cse-1. Then, its backend is available at localhost:18000 and has the CSE-ID in-cse-1.

Due to forwarding, the following requests will have the same results:

  • localhost:8000/onem2m and localhost:18000/~/mn-cse-1/onem2m
  • localhost:8000/onem2m and localhost:8000/~/mn-cse-1/onem2m"
  • localhost:8000/~/in-cse-1/onem2m and localhost:18000/onem2m

The following exaple illustrates this:

This file can be found here.

# Example 12b: Forwarding

from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest

client = OneM2MHTTPClient("http://localhost:8000", False)

onem2m_request = OneM2MRequest("retrieve", to="onem2m")
onem2m_response = client.send_onem2m_request(onem2m_request).get()
print "---> Request to: http://localhost:8000" + "/" + onem2m_request.to
print onem2m_response.to
#>>> onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> CSEBase(path='None', id='cb0')

onem2m_request = OneM2MRequest("retrieve", to="~/mn-cse-1/onem2m")
onem2m_response = client.send_onem2m_request(onem2m_request).get()
print "---> Request to: http://localhost:8000" + "/" + onem2m_request.to
print onem2m_response.to
#>>> ~/mn-cse-1/onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> CSEBase(path='None', id='cb0')

client.port = 18000
onem2m_request = OneM2MRequest("retrieve", to="~/mn-cse-1/onem2m")
onem2m_response = client.send_onem2m_request(onem2m_request).get()
print "---> Request to: http://localhost:18000" + "/" + onem2m_request.to
print onem2m_response.to
#>>> ~/mn-cse-1/onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> CSEBase(path='None', id='cb0')