23 KiB
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
andlocalhost:18000/~/mn-cse-1/onem2m
localhost:8000/onem2m
andlocalhost:8000/~/mn-cse-1/onem2m"
localhost:8000/~/in-cse-1/onem2m
andlocalhost: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')