Ship IoT with oneMPOWER, BeagleBoard, and Texas Instruments
In this post I connect Texas Instruments Sensortags to oneMPOWER™, a M2M/IoT device management platform and implementation of the oneM2M specification, developed by the OneM2M standards organization.
wot.io IoT middleware for the connected Enterprise
Here I build on previous work (part 1, part 2) done with TI Sensortags and the Beaglebone Black from beagleboard.org, to demonstrate how easy it is to combine data services in an IoT solution using wot.io.
As you will recall from those previous posts, I used the wot.io data service exchange™ to employ DeviceHive as a device management platform data service from which I routed device notifications through some transformation logic in scriptr; and on to a Nest thermostat integration in bip.io and monitoring & metering stripchart in Circonus.
While DeviceHive is an excellent, open-source option for device management, wot.io data service exchange is about choice and IoT platform interoperability.
Today we're going to demonstrate how to use an alternative device management platform with the wot.io data service exchange middleware and the oneMPOWER™ device management platform as a wot.io data service. The loose coupling of wot.io's routing architecture and data service adapters keep everything else working seamlessly, resulting in a powerful, composable IoT/M2M solution. While not demonstrated in this post, both DeviceHive and oneMPOWER could be deployed to work together in the wot.io data service exchange.
oneM2M & oneMPOWER
oneM2M represents an extensive set of entities and protocol bindings, designed to tackle complex device management and connectivity challenges in the M2M and IoT sector. Naturally, a full treatment of how the oneM2M system works is beyond the scope of this article, and I refer you to the oneM2M specifications if you want to learn more. For this demo, you'll want to refer to these in particular:
Additionally, you will soon find public code samples in github: [currently private for review]
One of the tools that InterDigital makes available to oneM2M developers is a client application designed to view the resource hierarchy in a given oneMPOWER system. We'll use it here to visualize state changes as we interact with the oneM2M HTTP bindings. At the top is a reference diagram of oneM2M entities, helpful to have at your fingertips. You can see events as they happen in the console window on top, and at the bottom is the resource viewer. Keep an eye there for resources to appear as they are created.
Note, the tool header lists MN-CSE, for a Middle Node, but we're working with an IN-CSE, an Infrastructure Node. These oneM2M designations are actually very similar—differentiated by their configuration to correspond to their roles in a large-scale oneM2M deployment. Don't worry about it for now, just watch the resource tree!
Application Entity Setup
For this demonstration, we will first create an Application Entity (AE) by hand, in the Common Services Entity (CSE) instantiated by the oneMPOWER server. In a full system, the devices or gateways would not typically be responsible for defining the full resource tree, so here we use curl
commands against the oneMPOWER HTTP REST interface endpoints. The message is sent as XML in the body of an HTTP POST, but per the specs you can use other encodings like JSON, too.
Note that all parts of the calls are important, with critical data represented in the headers, path, and body!
curl -i -X POST -H "X-M2M-RI: xyz1" -H "X-M2M-Origin: http://abc:0000/def" -H "Content-Type: application/vnd.onem2m-res+xml; ty=2" -H "X-M2M-NM: curlCommandApp_00" -d @payloadAe.xml "http://$IPADDRESS:$PORT/$CSE"
HTTP/1.1 201 Created
Content-Type: application/vnd.onem2m-res+xml
X-M2M-RI: xyz1
X-M2M-RSC: 2001
Content-Location: /CSEBase_01/def
Content-Length: 367
<?xml version="1.0"?>
<m2m:ae xmlns:m2m="http://www.onem2m.org/xml/protocols" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.onem2m.org/xml/protocols CDT-ae-v1_0_0.xsd" rn="curlCommandApp_00"><ty>2</ty><ri>def</ri><pi>CSEBase_01</pi><ct>20151030T221330</ct><lt>20151030T221330</lt><et>20151103T093330</et><aei>def</aei></m2m:ae>
The body of the POST contains this XML data, including the application ID for the AE:
<?xml version="1.0"?>
<m2m:ae xmlns:m2m="http://www.onem2m.org/xml/protocols" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.onem2m.org/xml/protocols CDT-ae-v1_0_0.xsd" rn="ae">
<api>myAppId</api>
<rr>false</rr>
</m2m:ae>
Verifying the AE
Next we'll perform a simple check to make sure that the Application Entity was properly configured in the CSE. We expect to get a reply showing what we configured for the AE, and no errors.
curl -i -X GET -H "X-M2M-RI: xyz1" -H "X-M2M-Origin: http://abc:0000/def" "http://$IPADDRESS:$PORT/$CSE/curlCommandApp_00"
HTTP/1.1 200 Content
Content-Type: application/vnd.onem2m-res+xml
X-M2M-RI: xyz1
X-M2M-RSC: 2000
Content-Length: 399
<?xml version="1.0"?>
<m2m:ae xmlns:m2m="http://www.onem2m.org/xml/protocols" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.onem2m.org/xml/protocols CDT-ae-v1_0_0.xsd" rn="curlCommandApp_00"><ty>2</ty><ri>def</ri><pi>CSEBase_01</pi><ct>20151030T221330</ct><lt>20151030T221330</lt><et>20151103T093330</et><api>myAppId</api><aei>def</aei><rr>false</rr></m2m:ae>
And you can see above, the ID myAppId
is there! It worked! We can also see it appear in the resource tree viewer, here shown as the green box labeled "def"
(a "foo"
name drawn from the create call above):
Create a Container
In order to store content in a CSE, you must first create a Container entity. This is just a named bucket into which your content instances will go. Here's the call to set up a container named curlCommandContainer_00
. The XML payload is more or less empty as the name implies, as we are not setting any extended attributes here.
curl -i -X POST -H "X-M2M-RI: xyz2" -H "X-M2M-Origin: http://abc:0000/$CSE/def" -H "Content-Type: application/vnd.onem2m-res+xml; ty=3" -H "X-M2M-NM: curlCommandContainer_00" -d @payloadContainerEmpty.xml "http://$IPADDRESS:$PORT/$CSE/curlCommandApp_00"
HTTP/1.1 201 Created
Content-Type: application/vnd.onem2m-res+xml
X-M2M-RI: xyz2
X-M2M-RSC: 2001
Content-Location: /CSEBase_01/def/cnt_20151030T221435_0
Content-Length: 407
<?xml version="1.0"?>
<m2m:cnt xmlns:m2m="http://www.onem2m.org/xml/protocols" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.onem2m.org/xml/protocols CDT-cnt-v1_0_0.xsd" rn="curlCommandContainer_00"><ty>3</ty><ri>cnt_20151030T221435_0</ri><pi>def</pi><ct>20151030T221435</ct><lt>20151030T221435</lt><et>20151103T093435</et><st>0</st><cni>0</cni><cbs>0</cbs></m2m:cnt>
And again, the viewer shows our container created successfully, in red. It's labeled by the resource identifier (also returned in the XML response we see above), and not by the resource name that we provided. (If you hover over the block you can verify the extra info is correct.)
Create a Content Instance
Now we're ready to get to the fun stuff, sending actual data from our devices! Before we go over to the device script, we'll run one more test to make sure we can create a Content Instance by hand.
Of note here is that each Content Instance needs a unique identifier. Here you can see its name specified by the request header X-M2M-NM: curlCommandContentInstance_00
. If you run the same command with the same name, it will fail, as the content instance already exists. This makes sure you can't accidentally erase important data.
curl -i -X POST -H "X-M2M-RI: xyz4" -H "X-M2M-Origin: http://abc:0000/$CSE/def/cnt_20151030T221435_0" -H "Content-Type: application/vnd.onem2m-res+xml; ty=4" -H "X-M2M-NM: curlCommandContentInstance_00" -d @payloadContentInstance.xml "http://$IPADDRESS:$PORT/$CSE/curlCommandApp_00/curlCommandContainer_00"
HTTP/1.1 201 Created
Content-Type: application/vnd.onem2m-res+xml
X-M2M-RI: xyz4
X-M2M-RSC: 2001
Content-Location: /CSEBase_01/def/cnt_20151030T221435_0/cin_20151030T221557_1
Content-Length: 417
<?xml version="1.0"?>
<m2m:cin xmlns:m2m="http://www.onem2m.org/xml/protocols" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.onem2m.org/xml/protocols CDT-cin-v1_0_0.xsd" rn="curlCommandContentInstance_00"><ty>4</ty><ri>cin_20151030T221557_1</ri><pi>cnt_20151030T221435_0</pi><ct>20151030T221557</ct><lt>20151030T221557</lt><et>20151103T093557</et><st>1</st><cs>2</cs></m2m:cin>
This is the content we sent in the body of the request, again as XML. You can see the data field in the con
element, which is the integer 22.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<m2m:cin xmlns:m2m="http://www.onem2m.org/xml/protocols" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.onem2m.org/xml/protocols CDT-cin-v1_0_0.xsd" rn="cin">
<cnf>text/plain:0</cnf>
<con>22</con>
</m2m:cin>
And our content instance appears in the viewer as well, in the orange block:
And you can see the details in a pop-up. Notice the parentID
, and that it matches the container's ID from above. You can also see the data we sent at the bottom, the value 22:
Send Device Data
Running on the BeagleBoard device, we have a small Python script that communicates with the oneM2M HTTP REST interface to send periodic telemetry data to the oneMPOWER instance, and ultimately on to the wot.io bus via the wot.io oneMPOWER adapter. First, the header, where we import some libs and set our configuration: the CSE name, app name, and container name must match what's been configured in the oneMPOWER instance.
#!/usr/bin/env python
import time
import httplib
import os
command_temp = "python ./sensortag.py 78:A5:04:8C:15:71 -Z -n 1"
hostname = "23.253.204.195"
port = 7000
csename = "CSE01"
appname = "curlCommandApp_00"
container = "curlCommandContainer_00"
Next, we set up some simple helper functions to
- read the sensor data from the TI SensorTags connecting to our device via Bluetooth (see previous post for details),
- compose a Content Instance XML message, and
- send it to the HTTP endpoint.
Finally, we loop and sleep to generate time-series data. Simple!
def readsensor(cmd):
return float(os.popen(cmd).read())
def onem2m_cin_body(value):
message = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<m2m:cin xmlns:m2m="http://www.onem2m.org/xml/protocols" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.onem2m.org/xml/protocols CDT-cin-v1_0_0.xsd" rn="cin">
<cnf>text/plain:0</cnf>
<con>%s</con>
</m2m:cin>""" % value
return message
def send(value):
body = onem2m_cin_body(value)
headers = {}
headers['Content-Length'] = "%d" % len(body)
headers['Content-Type'] = "application/vnd.onem2m-res+xml; ty=4"
headers['X-M2M-NM'] = "my_ci_id_%s" % time.time()
headers['X-M2M-RI'] = "xyz1"
headers['X-M2M-Origin'] = "http://abc:0000/def"
path = "/%s/%s/%s" % (csename, appname, container)
con = httplib.HTTPConnection(hostname, port)
con.request("POST", path, body, headers)
res = con.getresponse()
print res.status, res.reason, res.read()
con.close
while True:
print "Reading sensor\n"
value = readsensor(command_temp)
print "Got %f - sending\n" % value
send(value)
print "Sleeping...\n"
time.sleep(30)
And now a quick example of the output as we run the above script. We see it read the SensorTag data as we have done in the past, assemble a content instance message, and send it via HTTP POST. Created content instances appear in the specified container, just as we saw above, and from there the telemetry flows back to the wot.io bus and on to other data services.
root@beaglebone:~# ./main.py
Reading sensor
Got 44.643921 - sending
201 Created <?xml version="1.0"?>
<m2m:cin xmlns:m2m="http://www.onem2m.org/xml/protocols" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.onem2m.org/xml/protocols CDT-cin-v1_0_0.xsd" rn="my_ci_id_1446511119.68"><ty>4</ty><ri>cin_20151103T003839_9</ri><pi>cnt_20151030T221435_0</pi><ct>20151103T003839</ct><lt>20151103T003839</lt><et>20151106T115839</et><st>9</st><cs>13</cs></m2m:cin>
Sleeping...
oneMPOWER Protocol Analyzer
It's worth noting that there's also a protocol analyzer available in the resource tree viewer, which is handy for debugging communication sequences. You'll see some of our requests represented below:
Ship your IoT Solution with wot.io Data Services
As you will recall from my previous post, we have now done everything necessary to
- use temperature readings originating from TI Sensortags that were
- sent over Bluetooth LE to a BeagleBone Black acting as a
- gateway device being managed by oneMPOWER in order to,
- after first being transformed by business logic housed in scriptr,
- control our Nest thermostat
- from a custom bip.io workflow, and finally to
- set up monitoring and alerting for the whole thing using Circonus
Whew! That's a mouthful! What a relief that wot.io's loosely-coupled architecture supports the DRY principle so that we only had to modify the third bullet. The rest of that complex data flow just continued to work just like before!
From data in motion to data at rest, and with an ever-growing selection of data service partners, wot.io has you covered, including enterprise-ready solutions like oneMPOWER. Ready for more? Head over to wot.io and dig in!