-
Notifications
You must be signed in to change notification settings - Fork 1
4.3 Creating RAP plugin in other languages
At the beginning, the platform plugin application has to register to the generic RAP, sending a message to exchange symbIoTe.rapPluginExchange with key symbIoTe.rapPluginExchange.add-plugin, with some information included:
- the platform ID (a custom string, used )
- a boolean flag specifying it supports notifications,
- a boolean flag specifying it supports filters.
This is the message format expected during plugin registration:
{
type: "REGISTER_PLUGIN",
platformId: string,
hasNotifications: boolean,
hasFilters: boolean
}
e.g.:
{
"type": "REGISTER_PLUGIN",
"platformId": "platform",
"hasFilters": true,
"hasNotifications": true
}
Platform ID is used to specify which is the plugin that is going to handle the resource access request; this is needed in case of multiple plugins. Consequently, the same string has to be added also during resource registration (as an addidional parameter) and as routing key for rabbit messages during resource access (platformId.get, platformId.set, etc.).
If the platform can natively support filters/notifications, different configuration steps are required:
- Filters:
- If platform supports filters, RAP plugin just forwards filters to platform supporting filters
- (Optionally) a platform owner can decide to implement filters in RAP platform specific plugin
- If platform does not support filters the historical readings are retrieved without any filter
- Notifications:
- Enable/disable flag in CloudConfigProperties -> rap.northbound.interface.WebSocket=true/false
In order to receive messages for accessing resources, platform plugin shall create an exchange with name "plugin-exchange" and then bind to it the following:
- {platformId}.get
- {platformId}.set
- {platformId}.history
- {platformId}.subscribe
- {platformId}.unsubscribe
The interface exposed by RAP to applications is OData. In order to simplify plugin implementation, it does not expose this interface to platform plugins, and does map OData requests into JSON messages:
- OData request endpoint is split by "/", within "resourceInfo" property
- Filter is mapped into a specific format, within "filter" property
- Body is added to the message as it is, within "body" property
For a path like:
http://symbiote-cloud.com/rap/Entity1('id1')/Entity2('id2')/Collection1?$top=1
The endpoint (excluding domain name and "/rap/" part) is split into an ordered array of objects, each one made by three properties:
- type is the object name (i.e. Entity1, Entity2, Collection1 in the example)
- symbioteId is the object UIID that identifies the resource in symbIoTe and can be NULL if the object is not supposed to be registered into symbIoTe (e.g. Observation has no ID because is not an entity that you register as a resource)
- internalId is the object UIID that identifies the resource inside the platform and can be NULL if the object is not supposed to be registered into symbIoTe (e.g. Observation has no ID because it is not an entity that you register as a resource) So the corresponding JSON message will be:
{
"resourceInfo" : [
{
"symbioteId" : “id1",
"internalId" : “abcdefgh",
"type" : “Entity1"
},
{
“symbioteId" : “id2",
“internalId" : “ijklmnop",
"type" : “Entity2"
}, {
"type" : “Collection1"
}
],
"type" : “GET"
}
Last part with top is explained in reading history data.
The data that is expected to be returned by the RAP plugin from this request is a list of objects of type "Collection1". The information are related to a specific "Collection1", owned by the "Entity2" object with "id2" id, that, in turn, is contained into "Entity1" object with "id1" id.
Access features supported are (NB: the following examples refer to OData queries (e.g. /rap/Sensors('abcdefgh')/Observations&$top=1), where paths were split in JSON arrays):
- Read current value from resource, e.g.:
{
"resourceInfo" : [ {
"symbioteId" : "abcdefgh",
"internalId" : "123456",
"type" : "Light"
}, {
"type" : "Observation"
} ],
"type" : "GET"
}
Example of log from RabbitMQ:
Node: rabbit@localhost
Connection: 127.0.0.1:56779 -> 127.0.0.1:5672
Virtual host: /
User: guest
Channel: 14
Exchange: plugin-exchange
Routing keys: [<<"RapPluginExample.get">>]
Routed queues: [<<"bec056e4-c688-489a-8ba4-901955f314f5">>]
Properties: [{<<"reply_to">>,longstr,
<<"amq.rabbitmq.reply-to.g2dkABByYWJiaXRAbG9jYWxob3N0AABCLAAAAAEC.XGXofs0tuaOCi06L5ONqJw==">>},
{<<"priority">>,signedint,0},
{<<"delivery_mode">>,signedint,2},
{<<"headers">>,table,[]},
{<<"content_encoding">>,longstr,<<"UTF-8">>},
{<<"content_type">>,longstr,<<"text/plain">>}]
Payload:
{
"resourceInfo" : [ {
"symbioteId" : "sss",
"internalId" : "rp_isen1",
"type" : "Sensor"
}, {
"type" : "Observation"
} ],
"type" : "GET"
}
The plugin then needs to return response. As you can see in the incoming message is reply_to property and
plugin needs to send message back to that value.
If everything is OK then body is RapPluginOkResponse in following format:
{
"@c": ".RapPluginOkResponse",
"body": "{\"resourceId\":\"rp_isen1\",\"location\":{\"@c\":\".WGS84Location\",\"longitude\":48.2088475,\"latitude\":16.3734492,\"altitude\":158.0,\"name\":\"Stephansdome\",\"description\":[\"City of Wien\"]},\"resultTime\":\"2018-11-06T08:25:20\",\"samplingTime\":\"2018-11-06T08:25:19\",\"obsValues\":[{\"value\":\"7\",\"obsProperty\":{\"name\":\"Temperature\",\"iri\":\"TempIRI\",\"description\":[\"Air temperature\"]},\"uom\":{\"symbol\":\"C\",\"name\":\"degree Celsius\",\"iri\":\"C_IRI\",\"description\":null},\"featureOfInterest\":null}]}",
"responseCode": 200
}
As you can see in JSON is key @c which needs to be .RapPluginOkResponse. Then we have responseCode that is 200. This is the code that will be send back to HTTP client. And there is body which content is JSON encoded in string. The encoded JSON is on observation:
{
"resourceId": "rp_isen1",
"location": {
"@c": ".WGS84Location",
"longitude": 48.2088475,
"latitude": 16.3734492,
"altitude": 158.0,
"name": "Stephansdome",
"description": ["City of Wien"]
},
"resultTime": "2018-11-06T08:25:20",
"samplingTime": "2018-11-06T08:25:19",
"obsValues": [
{
"value": "7",
"obsProperty": {
"name": "Light",
"iri": "lightIRI",
"description": ["Light in room"]
},
"uom": {
"symbol": "lx",
"name": "Lux",
"iri": "lx_IRI",
"description": null
},
"featureOfInterest": null
}
]
}
Here is example of response from RabbitMQ log:
Node: rabbit@localhost
Connection: 127.0.0.1:59514 -> 127.0.0.1:5672
Virtual host: /
User: guest
Channel: 1
Exchange:
Routing keys: [<<"amq.rabbitmq.reply-to.g2dkABByYWJiaXRAbG9jYWxob3N0AABCLAAAAAEC.XGXofs0tuaOCi06L5ONqJw==">>]
Routed queues: []
Properties: [{<<"priority">>,signedint,0},
{<<"delivery_mode">>,signedint,2},
{<<"headers">>,table,
[{<<"__TypeId__">>,longstr,
<<"eu.h2020.symbiote.rapplugin.messaging.RapPluginOkResponse">>}]},
{<<"content_encoding">>,longstr,<<"UTF-8">>},
{<<"content_type">>,longstr,<<"application/json">>}]
Payload:
{"@c":".RapPluginOkResponse","body":"{\"resourceId\":\"rp_isen1\",\"location\":{\"@c\":\".WGS84Location\",\"longitude\":48.2088475,\"latitude\":16.3734492,\"altitude\":158.0,\"name\":\"Stephansdome\",\"description\":[\"City of Wien\"]},\"resultTime\":\"2018-11-06T08:25:20\",\"samplingTime\":\"2018-11-06T08:25:19\",\"obsValues\":[{\"value\":\"7\",\"obsProperty\":{\"name\":\"Temperature\",\"iri\":\"TempIRI\",\"description\":[\"Air temperature\"]},\"uom\":{\"symbol\":\"C\",\"name\":\"degree Celsius\",\"iri\":\"C_IRI\",\"description\":null},\"featureOfInterest\":null}]}","responseCode":200}
You can notice that routing key is corresponding to value of reply_to from request.
If plugin encountered error it can return error message back:
{
"@c": ".RapPluginErrorResponse",
"message": "only access on resource level supported",
"responseCode": 501
}
In error response instead of body there is message.
This is message payload:
{
"resourceInfo" : [ {
"symbioteId" : "abcdefgh",
"internalId" : "123456",
"type" : "EnvSensor"
}, {
"type" : "Observation"
} ],
"filter" : {
"type" : "expr",
"param" : "temperature",
"cmp" : "EQ",
"val" : "20"
},
"type" : "HISTORY"
}
RabbitMQ log message:
Node: rabbit@localhost
Connection: 127.0.0.1:56779 -> 127.0.0.1:5672
Virtual host: /
User: guest
Channel: 14
Exchange: plugin-exchange
Routing keys: [<<"RapPluginExample.history">>]
Routed queues: [<<"6e2a54cc-7b2b-4bc2-93e9-db0dfa56f5a3">>]
Properties: [{<<"reply_to">>,longstr,
<<"amq.rabbitmq.reply-to.g2dkABByYWJiaXRAbG9jYWxob3N0AABCLAAAAAEC.uVzRjSZOgRzwEBWqK4dKaw==">>},
{<<"priority">>,signedint,0},
{<<"delivery_mode">>,signedint,2},
{<<"headers">>,table,[]},
{<<"content_encoding">>,longstr,<<"UTF-8">>},
{<<"content_type">>,longstr,<<"text/plain">>}]
Payload:
{
"resourceInfo" : [ {
"symbioteId" : "abcdefgh",
"internalId" : "123456",
"type" : "EnvSensor"
}, {
"type" : "Observation"
} ],
"filter" : {
"type" : "expr",
"param" : "temperature",
"cmp" : "EQ",
"val" : "20"
},
"top" : 10,
"type" : "HISTORY"
}
In order to get current reading or a history of readings, the OData request can be made by using the $top operator. Also read history values can be received with or without filters (using $filter operator) depending on whether the plugin is supporting filters or not:
- if $top is 1 we read current observation
- if $top is >1 we assume historical data are requested -> type : "HISTORY" -> top property added to the JSON message
- $filter is mapped as a composition of -> filter: a list of filters (filter / expr) linked by a logic operation (LOP) -> expr: a triple composed by parameter (param), comparator (cmp), value (val)
For a request like:
GET /rap/Entity1('id1')/Collection1?$top=20
The JSON message translated from
{
"resourceInfo" : [
{
"symbioteId" : "id1",
"internalId" : "abcdefgh",
"type" : "Entity1"
},
{
"type" : "Collection1"
}
],
"top" : 20,
"type" : “HISTORY"
}
e.g.:
GET http://{url}/rap/Sensor('test')/Observations?$top=30&filter=madeFrom/name eq 'Rome'
{
"resourceInfo" : [ {
"symbioteId" : "test",
"internalId" : "p_test",
"type" : "Sensor"
}, {
"type" : "Observation"
} ],
"top" : 30,
"filter" : {
"type" : "expr",
"param" : "[madeFrom, name]",
"cmp" : "EQ",
"val" : “Rome"
},
"type" : "HISTORY"
}
Response body from history is array of observations similar to response for current reading.
When such request is requested, the body of the message will also include parameters needed for the actuation, in a format that depends on the resource accessed. This is template of message that is sent to plugin:
{
"resourceInfo" : [ {
"symbioteId" : "{symbioteId}",
"internalId" : "{internalId}",
"type" : "{Model}"
} ],
"body" : {
"{capability}": [
{ "{restriction}": "{value}" }
]
},
"type" : "SET"
}
In case of Actuators they need to have Capabilities in the model/registration. Lets assume that we have following example of registration:
[
{
"internalId": "rp_iaid1",
"pluginId": "RapPluginExample",
"accessPolicy": {
"policyType": "PUBLIC",
"requiredClaims": {}
},
"filteringPolicy": {
"policyType": "PUBLIC",
"requiredClaims": {}
},
"resource": {
"@c": ".Actuator",
"id": "aaa",
"name": "Light 1",
"description": [
"This is light 1"
],
"services": null,
"capabilities": [
{
"name": "OnOffCapabililty",
"parameters": [
{
"name": "on",
"mandatory": true,
"datatype": {
"@c": ".PrimitiveDatatype",
"baseDatatype": "http:\/\/www.w3.org\/2001\/XMLSchema#boolean"
}
}
]
},
{
"name": "RGBCapability",
"parameters": [
{
"name": "r",
"mandatory": true,
"restrictions": [
{
"@c": ".RangeRestriction",
"min": 0,
"max": 100
}
],
"datatype": {
"@c": ".PrimitiveDatatype",
"baseDatatype": "http:\/\/www.w3.org\/2001\/XMLSchema#int"
}
},
{
"name": "g",
"mandatory": true,
"restrictions": [
{
"@c": ".RangeRestriction",
"min": 0,
"max": 100
}
],
"datatype": {
"@c": ".PrimitiveDatatype",
"baseDatatype": "http:\/\/www.w3.org\/2001\/XMLSchema#int"
}
},
{
"name": "b",
"mandatory": true,
"restrictions": [
{
"@c": ".RangeRestriction",
"min": 0,
"max": 100
}
],
"datatype": {
"@c": ".PrimitiveDatatype",
"baseDatatype": "http:\/\/www.w3.org\/2001\/XMLSchema#int"
}
}
]
}
],
"locatedAt": {
"@c": ".WGS84Location",
"longitude": 2.349014,
"latitude": 48.864716,
"altitude": 15,
"name": "Paris",
"description": [
"This is paris"
]
},
"interworkingServiceURL": "https://symbiotedoc.tel.fer.hr"
}
}
]
The message that is actuating resource is in following template:
{
"resourceInfo" : [
{
"symbioteId" : "some symbiote id",
"internalId" : "soem internal id",
"type" : "some entity"
}
],
"body" : "{ \"{capability1}\": [ { \"{parameter1}\": \"{value1}\" } ], \"{capability2}\": [ { \"{parameter2}\": \"{value2}\" }, { \"{parameter3}\": \"{value3}\" } ] }",
"type" : "SET"
}
The value of body is JSON encoded in string. The template of that JSON is:
{
"{capability1}": [{ "{parameter1}": "{value1}" }],
"{capability2}": [
{ "{parameter2}": "{value2}" },
{ "{parameter3}": "{value3}" }
]
}
For our concrete example payload is this:
{
"resourceInfo" : [ {
"symbioteId" : "aaa",
"internalId" : "rp_iaid1",
"type" : "Actuator"
} ],
"body" : "{\n \"OnOffCapabililty\" : [\n {\n \"on\" : true\n }\n ]\n}",
"type" : "SET"
}
RabbitMQ log is following:
Node: rabbit@localhost
Connection: 127.0.0.1:56779 -> 127.0.0.1:5672
Virtual host: /
User: guest
Channel: 14
Exchange: plugin-exchange
Routing keys: [<<"RapPluginExample.set">>]
Routed queues: [<<"b63201bb-9a34-476c-93b6-4df570434166">>]
Properties: [{<<"reply_to">>,longstr,
<<"amq.rabbitmq.reply-to.g2dkABByYWJiaXRAbG9jYWxob3N0AABCLAAAAAEC.BFcu/EXHE78Gi6OrGyYjUw==">>},
{<<"priority">>,signedint,0},
{<<"delivery_mode">>,signedint,2},
{<<"headers">>,table,[]},
{<<"content_encoding">>,longstr,<<"UTF-8">>},
{<<"content_type">>,longstr,<<"text/plain">>}]
Payload:
{
"resourceInfo" : [ {
"symbioteId" : "aaa",
"internalId" : "rp_iaid1",
"type" : "Actuator"
} ],
"body" : "{\n \"OnOffCapabililty\" : [\n {\n \"on\" : true\n }\n ]\n}",
"type" : "SET"
}
The response can be either error or successful response. RabbitMQ log for successful response is:
Node: rabbit@localhost
Connection: 127.0.0.1:56972 -> 127.0.0.1:5672
Virtual host: /
User: guest
Channel: 3
Exchange:
Routing keys: [<<"amq.rabbitmq.reply-to.g2dkABByYWJiaXRAbG9jYWxob3N0AABCLAAAAAEC.BFcu/EXHE78Gi6OrGyYjUw==">>]
Routed queues: []
Properties: [{<<"priority">>,signedint,0},
{<<"delivery_mode">>,signedint,2},
{<<"headers">>,table,
[{<<"__TypeId__">>,longstr,
<<"eu.h2020.symbiote.rapplugin.messaging.RapPluginOkResponse">>}]},
{<<"content_encoding">>,longstr,<<"UTF-8">>},
{<<"content_type">>,longstr,<<"application/json">>}]
Payload:
{"@c":".RapPluginOkResponse","body":null,"responseCode":204}
As we can see response form actuation if it is successful does not have body. That is the reason that HTTP response code is 204.
Invoking service is similar. The difference is in the payload. Services don't have Capabilities. They only have parameters and they can have result.
The template for request is following:
{
"resourceInfo" : [ {
"symbioteId" : "serv",
"internalId" : "rp_isrid1",
"type" : "Service"
} ],
"body" : "[{\"parameter1\": value1}, {\"parameter2\":value2}]",
"type" : "SET"
}
Here the body has value of JSON encoded in string. The JSON is in format:
[
{"parameter1": value1},
{"parameter2": value2}
]
For example if we have registration of service like this:
[
{
"internalId": "rp_isrid1",
"pluginId": "RapPluginExample",
"accessPolicy": {
"policyType": "PUBLIC",
"requiredClaims": {}
},
"filteringPolicy": {
"policyType": "PUBLIC",
"requiredClaims": {}
},
"resource": {
"@c": ".Service",
"id": "serv",
"name": "Light service 1",
"description": [
"This is light service 1"
],
"interworkingServiceURL": "https://symbiotedoc.tel.fer.hr",
"parameters": [
{
"name": "inputParam1",
"mandatory": true,
"restrictions": [
{
"@c": ".LengthRestriction",
"min": 2,
"max": 10
}
],
"datatype": {
"@c": ".PrimitiveDatatype",
"isArray": false,
"baseDatatype": "http:\/\/www.w3.org\/2001\/XMLSchema#string"
}
}
],
"resultType" : {
"@c": ".PrimitiveDatatype",
"isArray": false,
"baseDatatype": "http:\/\/www.w3.org\/2001\/XMLSchema#string"
}
}
}
]
then the request will look like this:
{
"resourceInfo" : [ {
"symbioteId" : "serv",
"internalId" : "rp_isrid1",
"type" : "Service"
} ],
"body" : "[{\"inputParam1\":\"on\"}]",
"type" : "SET"
}
RabbitMQ log of that request:
Node: rabbit@localhost
Connection: 127.0.0.1:56779 -> 127.0.0.1:5672
Virtual host: /
User: guest
Channel: 14
Exchange: plugin-exchange
Routing keys: [<<"RapPluginExample.set">>]
Routed queues: [<<"6ea9eed1-36ae-4415-9117-43e7c7714f86">>]
Properties: [{<<"reply_to">>,longstr,
<<"amq.rabbitmq.reply-to.g2dkABByYWJiaXRAbG9jYWxob3N0AABCLAAAAAEC.D4xCQUfSMJFBaP5H2RIT/w==">>},
{<<"priority">>,signedint,0},
{<<"delivery_mode">>,signedint,2},
{<<"headers">>,table,[]},
{<<"content_encoding">>,longstr,<<"UTF-8">>},
{<<"content_type">>,longstr,<<"text/plain">>}]
Payload:
{
"resourceInfo" : [ {
"symbioteId" : "serv",
"internalId" : "rp_isrid1",
"type" : "Service"
} ],
"body" : "[{\"inputParam1\":\"on\"}]",
"type" : "SET"
}
The response is similar to actuator response with on difference. That service can return result. In the example response from RabbitMQ log is:
Node: rabbit@localhost
Connection: 127.0.0.1:57241 -> 127.0.0.1:5672
Virtual host: /
User: guest
Channel: 3
Exchange:
Routing keys: [<<"amq.rabbitmq.reply-to.g2dkABByYWJiaXRAbG9jYWxob3N0AABCLAAAAAEC.D4xCQUfSMJFBaP5H2RIT/w==">>]
Routed queues: []
Properties: [{<<"priority">>,signedint,0},
{<<"delivery_mode">>,signedint,2},
{<<"headers">>,table,
[{<<"__TypeId__">>,longstr,
<<"eu.h2020.symbiote.rapplugin.messaging.RapPluginOkResponse">>}]},
{<<"content_encoding">>,longstr,<<"UTF-8">>},
{<<"content_type">>,longstr,<<"application/json">>}]
Payload:
{"@c":".RapPluginOkResponse","body":"\"ok\"","responseCode":200}
The notifications mechanism follows a different flow than the direct resource access and needs a specific rabbitMQ queues to be used.
- The platform plugin will receive subscription/unsubscription requests from the plugin-exchange, using subscribe/unsubscribe topic keys. The message will contain a list of resource IDs. (see 3.4.5 Push feature). RAP Plugin is supposed to send notifications for the subscribed resource(s), from this point forward.
- Notifications should be sent from RAP plugin to generic RAP to RabbitMQ exchange symbIoTe.rapPluginExchange-notification with a routing key symbIoTe.rapPluginExchange.plugin-notification.
All returned messages from read accesses (GET, HISTORY and notifications) are modeled as an instance of eu.h2020.symbiote.model.cim.Observation class, e.g.:
[
{
"resourceId": "abcdefgh",
"location": {
"longitude": 150.89,
"latitude": 23.56,
"altitude": 343.74
},
"resultTime": "2017-05-17T10:35:50",
"samplingTime": "2017-05-17T10:35:50",
"obsValues": [
{
"value": 21,
"uom": {
"symbol": "°C",
"label": "Celsius",
"comment": "Celsius degrees" },
"obsProperty": {
"label": "temperature",
"comment": "temperature in degrees"
}
}
]
}
]

Getting Started
Migration to 3.0.0
Migration to Docker
-
Preparation steps
1.1. Register user and configure platform in symbIoTe Core
1.2. Installation of required tools for symbIoTe platform components
1.3. Downloading jars
1.4. Downloading sources -
Configuring and starting components
2.1. Configuration of NGINX
2.2. Starting third party tools that are prerequisite for symbIoTe
2.3. Starting (generic) symbIoTe Cloud components
2.4. Configuration of cloud components
2.4.1. Starting symbIoTe Cloud components
2.5. Setting up the Platform Authentication and Authorization Manager (PAAM)
2.6. Starting Registration Handler and resource management
2.7. Set up of Resource Access Proxy
2.8. Manage resources
2.9. Set up of the Monitoring component
2.10. Other configuration topics -
Test integrated resource
3.1. Security
3.2. Search for resources
3.3. Obtaining resource access URL
3.4. Accessing the resource and actuating and invoking service for default (dummy) resources -
Creating RAP plugin
4.1. Customizing internal RAP plugin
4.2. Using RAP plugin starter
4.3. Creating RAP plugin in other languages -
Resource Description Examples
5.1. JSON Description Examples
5.2. RDF Description Examples - Preparation for L2 compliance
-
Configuring and starting components for L2
7.1. Starting Federation Manager
7.2. Starting Subscription Manager
7.3. Starting Platform Registry
7.4. Starting Trust Manager
7.5. Starting Bartering And Trading
7.6. Starting SLA Manager
7.7. Create a federation
7.8. Manage resources in L2
7.9. Register Subscription - Test Integrated L2 Resources
- Developing symbIoTe enabled apps