Murano Application IoT-Connector Integration

Create a Murano Application

Go to the Murano IoT-Marketplace. Under Applications find the Application From Scratch Template element. Select the element and click on the CREATE APPLICATION button.

To test my application I have already created a murano product solution, in the same business, from the IoT-Connector template. Products in the same business are by default available as services to application solutions, but we need to manually enable it in our application product setup. In my case I will enable iotconnectordev product that I created for testing.

enable product

Once enabled, it should show up as a service in the list to the left.

service list with product

IoT-Connector integration behind the scenes

IoT-Connector can organize devices into virtual groups. Each group can contain (zero-many) devices, users and applications.

- Group
  |- Devices
  |- Users
  |- Applications

Users can see and manage applications and devices in the groups they belong. Applications can manage and receive events from all devices in the same group.

Anonymous groups

When we enabled the iotconnectordev product in our Application, a new group will automatically be created with our Murano application added. In this case the group don't have any Users, and the only way to add and manage devices in the group is to use the IoT-Connector Application API.

This allows us to develop IoT Applications that provides tightly integrated device claiming and management UI while at the same time have access to the landscape of IoT-Connector enabled products.

User managed groups

It is also possible to add our application to an existing, user created group. In this case the user first need to go to the "Applications" tab in the IoT-Connector portal and select "Connect App".

iot connector portal connect app

You will then get a popup to give the App a name and generate a token.

iot connector portal generate token

The generated token can then be added to the IoT-Connector product settings under the Application Services tab. This will authenticate our solution against the group and add it as an application (ready to receive device events etc.).

murano add token

A single Murano application can only belong to one group at a time, so adding a token will remove the previously created anonymous group.

IoT-Connector Application API

IoT-Connector exposes a subset of device2 service API's as well as some service calls specific to the connector template.

For Murano Applications this means that your existing solutions will most likely already be IoT connector compatible without any code change.

<product>.setIdentityState()
<product>.getIdentity()
<product>.getIdentityState()
<product>.listIdentities()

See device2 documentation for more details on how to use this API.

For applications that provides a more seamless experience there are additional API's that gives solution access to IoT-Connector internal features.

<product>.claimDevice({code = "", context = ""})
<product>.getTempClaimURL({context = ""})

getTempClaimURL returns a URL to the IoT-Connector portal device claiming UI with a temporary session token. This token will allow a user to claim devices into the same IoT connector group that our application belongs to.

claimDevice will claim device(s) into the current IoT-Connector group.

The context parameter is optional for both and allows us to add an application specific context to every claimed device. For multi-tenant applications this could be an application specific user id to know which application user claimed this device.

Adding endpoints to our Application

In order to test the avaliable IoT-Connector API's we will add two web endpoints to our application that will call methods on our IoT-Connector product.

Webservice endpoints can be added by first pressing the ENDPOINTS tab to the left.

endpoints tab

And then +NEW ENDPOINT button in the top right corner.

murano add token

Product device listing

First we will add a device listing endpoint at. GET /api/v1/{product}/devices

add route

we will add the following code that will:

  1. fetch a list of connected products.
  2. verify that the product parameter is a valid connected product.
  3. call listIdentities API on that product and return the result.

devices endpoint

local product_list = Config.listService({type = "product"})
local product_map = {}
for _, product in ipairs(product_list) do
  product_map[product.script_key] = product.service
end


local service = product_map[request.parameters.product]

if service then
  local operation = "listIdentities"
  response.message = murano.services[service][operation]()
else
  response.message = "no such product"
  response.code = 404
end

Device claiming

Next we will add an endpoint to test the device claiming functionality. GET /api/v1/{product}/claim/{code} Similar to the previous endpoint, this endpoint will do the following:

  1. fetch a list of connected products.
  2. verify that the product parameter is a valid connected product.
  3. call claimDevices API on that product with the provided code parameter as argument and return the result.

claim endpoint

local product_list = Config.listServices({type = "product"})
local product_map = {}
for _, product in ipairs(product_list) do
  product_map[product.script_key] = product.service
end


local service = product_map[request.parameters.product]

if service then
  local operation = "claimDevices"
  response.message = murano.services[service][operation]({codes = {request.parameters.code}})
else
  response.message = "no such product"
  response.code = 404
end

Add a device to our IoT-Connector product

Go to your IoT-Connector product in the Murano UI, click devices and then +NEW DEVICE(S).

add devies

we will add a device called device001.

add device001

After we have added our device we will manually set device credentials for it.

set device credentials

set device credentials

Modify the following script with your product id and device credentials and save as send_data.sh

#!/bin/bash
  curl -v -i https://<your_product_id>.m2.exosite.io/onep:v1/stack/alias?state \
      -H "X-Exosite-CIK: <your_device_credentials>" \
      -H "Accept: application/x-www-form-urlencoded; charset=utf-8" \
      -d "$1"

We can now set a claim_code for our device to use, ex: ./send_data_demo.sh claim_code=device001

In case we want to reset the claim (remove owner) we can send the following data to the IoT-Connector: ./send_data_demo.sh reset=1

Test our endpoints

If we get our current device list, we will see that it is currently empty.

> curl https://<yourappname>.apps.exosite.io/api/v1/<yourproductname>/devices
> {"devices":{},"mayLoadMore":false}

We can claim our device001 using the claim API

> curl https://<yourappname>.apps.exosite.io/api/v1/<yourproductname>/claim/device001
> {"device001":{}}

If we call the list API again we will now see our device

> curl https://<yourappname>.apps.exosite.io/api/v1/<yourproductname>/devices
> {"devices":[{"identity":"device001","lastip":"211.75.143.219","lastseen":1562303143046084,"online":false,"state":{"data_in":{"reported":"3","set":"3","timestamp":1561554542336156}}}],"mayLoadMore":false}

Calling the claim device api again with the same code will return error 2 indicating that the device has already been claimed.

> curl https://<yourappname>.apps.exosite.io/api/v1/<yourproductname>/claim/device001
> {"device001":{"error":2}}

Giving an invalid code will result in an empty response

> curl https://<yourappname>.apps.exosite.io/api/v1/<yourproductname>/claim/wrongcode
> {}

Add claim context

Let us update our device claiming API to set a context as part of the claim.

GET /api/v1/{product}/claim/{code}

local product_list = Config.listServices({type = "product"})
local product_map = {}
for _, product in ipairs(product_list) do
  product_map[product.script_key] = product.service
end


local service = product_map[request.parameters.product]

if service then
  local operation = "claimDevices"
  response.message = murano.services[service][operation]({codes = {request.parameters.code}, context = "test"})
else
  response.message = "no such product"
  response.code = 404
end

Notice the added context = "test" to the murano.services[service][operation]() call.

Let us reset our device (so it can be claimed again) ./send_data_demo.sh reset=1

We can claim our device001 using the claim API

> curl https://<yourappname>.apps.exosite.io/api/v1/<yourproductname>/claim/device001
> {"device001":{}}

If we now call the device list API, there is a new tags field with a claim_context tag with value set to 'test'

> curl https://<yourappname>.apps.exosite.io/api/v1/<yourproductname>/devices
> {"devices":[{"identity":"device001","lastip":"211.75.143.219","lastseen":1562578799934194,"online":false,"state":{"data_in":{"reported":"3","set
":"3","timestamp":1561554542336156}},"tags":[{"name":"claim_context","value":"test"}]}],"mayLoadMore":false}

React to device events

Our application will also receive events from claimed devices.

We can dynamically handle these events, even as products are added/removed from our application, by creating a config_fallback handler function in the Config service.

This is a generic event fallback, for all events that does not have dedicated event handlers.

We will add a simple check in our handler to make sure the event is coming from our IoT-Connector devices, by validating the type is product and the event name is event (the even name emitted by device2 service). You can also validate the id of the IoT-Connector product from context.service attribute. The event payload object is provided by config_fallback handler as an argument variable called event.

--[[
Use this script editor to add and maintain eventhandlers for this service.
See documentation to learn more.
(/reference/services/)
--]]
if context.service_type == "product" and context.event == "event" then
  print(event)
end

fallback event handler

Now if we send an event to our device, we should see it printed in our application murano log.

> ./send_data_demo.sh temp=2.9

murano app log

Conclusion

We now have a sample application that connects to an IoT-Connector product and can list and claim devices using device claim codes that can be extended into a fully featured application with UI and authentication.