Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 45 additions & 25 deletions doc/advanced-topics.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ curl http://${KEYSTONE_HOST}/v3/OS-TRUST/trusts \
Apart from the generation of the trust, the use of secured Context Brokers should be transparent to the user of the IoT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this PR provides "the definitive fix" mentioned in #1012 (comment)

Thus, an entry in CHANGES_NEXT_RELEASE relating issue #1012 should be included.

Agent.

### GeoJSON support (this only applies for NGSI-LD, not for NGSI-v1 and NGSI-v2)
### GeoJSON support

The defined `type` of any GeoJSON attribute can be any set to any of the standard NGSI-v2 GeoJSON types - (e.g.
`geo:json`, `geo:point`). NGSI-LD formats such as `GeoProperty`, `Point` and `LineString` are also accepted `type`
The defined `type` of any GeoJSON attribute can be any set to any of the standard **NGSI-v2** GeoJSON types - (e.g.
`geo:json`, `geo:point`). **NGSI-LD** formats such as `GeoProperty`, `Point` and `LineString` are also accepted `type`
values. If the latitude and longitude are received as separate measures, the
[expression language](expressionLanguage.md) can be used to concatenate them.

Expand All @@ -93,15 +93,16 @@ values. If the latitude and longitude are received as separate measures, the
}
```

For `attributes` and `static_attributes` which need to be formatted as GeoJSON values, three separate input formats are
accepted. Provided the `type` is provisioned correctly, the `value` may be defined using any of the following formats:
For `attributes` and `static_attributes` which need to be formatted as GeoJSON values, three separate input
formats are accepted. Provided the `type` is provisioned correctly, the `value` may be defined using any of
the following formats:

- a comma delimited string
- a comma delimited string

```json
{
"name": "location",
"value": "23, 12.5"
"name": "location",
"value": "23, 12.5"
}
```

Expand All @@ -126,6 +127,27 @@ accepted. Provided the `type` is provisioned correctly, the `value` may be defin
}
```

Because GeoJSON types (e.g. `Point`, `LineString` etc.) are native types in **NGSI-LD**, automatic GeoJSON conversion is switched on for NGSI-LD by default.

With **NGSI-v2**, for backwards compatibility reasons, automatic GeoJSON conversion for types other than `geo:json` is turned off by default.
Add the `autocast` configuration to the attribute to enable GeoJSON conversion. Each GeoJSON attribute can be provisioned as shown:

```json
Copy link
Member

@fgalan fgalan Apr 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example in documentation is for a configuration group. However, the templates modified in this PR are only about devices:

imagen

The behavior should be as follow:

  • It can be enabled/disabled at configuration group level
  • It can be enabled/disabled at device level. If not defined at device level, the config group setting is used (and if not defined at config group level, then assume false by default)

{
"entity_type": "GPS",
"resource": "/iot/d",
"protocol": "PDI-IoTA-JSON",
..etc
"attributes": [
{
"name": "observationSpace",
"type": "Polygon",
"autocast" : "true"
}
]
}
```

### Metadata support

Both `attributes` and `static_attributes` may be supplied with metadata when provisioning an IoT Agent, so that the
Expand Down Expand Up @@ -173,7 +195,7 @@ following:
- Temporal Properties (e.g. `Datetime`, `Date` , `Time`)
- GeoJSON types (e.g `Point`, `LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, `MultiPolygon`)

Most NGSI-LD attributes are sent to the Context Broker as _properties_. If a GeoJSON type or native JSON type is
Most **NGSI-LD** attributes are sent to the Context Broker as _properties_. If a GeoJSON type or native JSON type is
defined, the data will be converted to the appropriate type. Temporal properties should always be expressed in UTC,
using ISO 8601. This ISO 8601 conversion is applied automatically for the `observedAt` _property-of-a-property_ metadata
where present.
Expand Down Expand Up @@ -207,12 +229,11 @@ Other unrecognised `type` attributes will be passed as NGSI-LD data using the fo
}
```


### NGSI-LD Linked Data support

`static_attributes` may be supplied with an additional `link` data element when provisioning an IoT Agent to ensure that
active attributes from the provisioned IoT Device may be maintained in parallel with a linked data entity . Take for
example a temperature gauge placed within a building. The **Device** data model literally represents the IoT device
itself, but the `temperature` attribute also needs to be shared with the **Building** entity
`static_attributes` may be supplied with an additional `link` data element when provisioning an IoT Agent to ensure that active attributes from the provisioned IoT Device may be maintained in parallel with a linked data entity . Take for example a temperature gauge placed within a building.
The **Device** data model literally represents the IoT device itself, but the `temperature` attribute also needs to be shared with the **Building** entity

A `link` between them can be provisioned as shown:

Expand Down Expand Up @@ -246,8 +267,7 @@ e.g.:
}
```

Whenever a `temperature` measure is received **Device** is updated, and entity `urn:ngsi-ld:Building:001` is also
updated as shown:
Whenever a `temperature` measure is received **Device** is updated, and entity `urn:ngsi-ld:Building:001` is also updated as shown:

```json
"temperature": {
Expand All @@ -263,25 +283,25 @@ updated as shown:

### Autoprovision configuration (autoprovision)

By default, when a measure arrives to the IoTAgent, if the `device_id` does not match with an existing one, then, the IoTA
creates a new device and a new entity according to the group config. Defining the field `autoprovision` to `false`
when provisioning the device group, the IoTA to reject the measure at the southbound, allowing only to persist the
data to devices that are already provisioned. It makes no sense to use this field in device provisioning since it is
By default, when a measure arrives to the IoTAgent, if the `device_id` does not match with an existing one, then, the IoTA
creates a new device and a new entity according to the group config. Defining the field `autoprovision` to `false`
when provisioning the device group, the IoTA to reject the measure at the southbound, allowing only to persist the
data to devices that are already provisioned. It makes no sense to use this field in device provisioning since it is
intended to avoid provisioning devices (and for it to be effective, it would have to be provisional).

### Explicitly defined attributes (explicitAttrs)

If a given measure element (object_id) is not defined in the mappings of the device or group provision, the measure is stored
If a given measure element (object_id) is not defined in the mappings of the device or group provision, the measure is stored
in the Context Broker by adding a new attribute to the entity with the same name of the undefined measure element. By adding the
field `explicitAttrs` with `true` value to device or group provision, the IoTAgent rejects the measure elements that are not defined
field `explicitAttrs` with `true` value to device or group provision, the IoTAgent rejects the measure elements that are not defined
in the mappings of device or group provision, persisting only the one defined in the mappings of the provision. If `explicitAttrs`
is provided both at device and group level, the device level takes precedence.

### Configuring operation to persist the data in Context Broker (appendMode)

This is a flag that can be enabled by activating the parameter `appendMode` in the configuration file or by using the `IOTA_APPEND_MODE`
environment variable (more info [here](https://github.com/telefonicaid/iotagent-node-lib/blob/master/doc/installationguide.md)).
If this flag is activated, the update requests to the Context Broker will be performed always with APPEND type, instead of the
environment variable (more info [here](https://github.com/telefonicaid/iotagent-node-lib/blob/master/doc/installationguide.md)).
If this flag is activated, the update requests to the Context Broker will be performed always with APPEND type, instead of the
default UPDATE. This have implications in the use of attributes with Context Providers, so this flag should be used with care.

### Data mapping plugins
Expand Down Expand Up @@ -339,7 +359,7 @@ The library provides some plugins out of the box, in the `dataPlugins` collectio
use the `addQueryMiddleware` and `addUpdateMiddleware` functions with the selected plugin, as in the example:

```javascript
var iotaLib = require('iotagent-node-lib');
var iotaLib = require("iotagent-node-lib");

iotaLib.addUpdateMiddleware(iotaLib.dataPlugins.compressTimestamp.update);
iotaLib.addQueryMiddleware(iotaLib.dataPlugins.compressTimestamp.query);
Expand All @@ -349,7 +369,7 @@ iotaLib.addQueryMiddleware(iotaLib.dataPlugins.compressTimestamp.query);

This plugins change all the timestamp attributes found in the entity, and all the timestamp metadata found in any
attribute, from the basic complete calendar timestamp of the ISO8601 (e.g.: 20071103T131805) to the extended complete
calendar timestamp (e.g.: +002007-11-03T13:18). The middleware expects to receive the basic format in updates and return
calendar timestamp (e.g.: `+002007-11-03T13:18`). The middleware expects to receive the basic format in updates and return
it in queries (and viceversa, receive the extended one in queries and return it in updates).

##### Attribute Alias plugin (attributeAlias)
Expand Down
4 changes: 2 additions & 2 deletions doc/installationguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ used for the same purpose. For instance:
standard, but the final decision has yet been confirmed), take into account it could change
- **explicitAttrs**: if this flag is activated, only provisioned attributes will be processed to Context Broker. This
flag is overwritten by `explicitAttrs` flag in group or device provision.
- **defaultEntityNameConjunction**: the default conjunction string used to compose a default `entity_name` when is not
provided at device provisioning time; in that case `entity_name` is composed by `type` + `:` + `device_id`.
- **defaultEntityNameConjunction**: the default conjunction string used to compose a default `entity_name` when is not
provided at device provisioning time; in that case `entity_name` is composed by `type` + `:` + `device_id`.
Default value is `:`. This value is overwritten by `defaultEntityNameConjunction` in group provision.
- **relaxTemplateValidation**: if this flag is activated, `objectId` attributes for incoming devices are not
validated, and may exceptionally include characters (such as semi-colons) which are
Expand Down
6 changes: 4 additions & 2 deletions lib/services/devices/devices-NGSI-v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ function createInitialEntityNgsi2(deviceData, newDevice, callback) {
for (const att in options.json) {
try {
// Format any GeoJSON attrs properly
options.json[att] = NGSIv2.formatGeoAttrs(options.json[att]);
const attrInfo = _.findWhere(deviceData.active, {name: att})
options.json[att] = NGSIv2.formatGeoAttrs(options.json[att], attrInfo);
} catch (error) {
return callback(new errors.BadGeocoordinates(JSON.stringify(options.json)));
}
Expand Down Expand Up @@ -296,7 +297,8 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) {
for (const att in options.json) {
try {
// Format any GeoJSON attrs properly
options.json[att] = NGSIv2.formatGeoAttrs(options.json[att]);
const attrInfo = _.findWhere(deviceData.active, {name: att});
options.json[att] = NGSIv2.formatGeoAttrs(options.json[att], attrInfo);
} catch (error) {
return callback(new errors.BadGeocoordinates(JSON.stringify(options.json)));
}
Expand Down
79 changes: 44 additions & 35 deletions lib/services/ngsi/entities-NGSI-v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

/* eslint-disable consistent-return */

const _ = require('underscore');
const request = require('request');
const statsService = require('./../stats/statsRegistry');
const async = require('async');
Expand All @@ -49,45 +50,51 @@ const context = {
* @param {Object} attr Attribute to be analyzed
* @return {Object} GeoJSON version of the attribute
*/
function formatGeoAttrs(attr) {
function formatGeoAttrs(attr , attrInfo) {


const obj = attr;

if (attr.type) {
switch (attr.type.toLowerCase()) {
if (attr.value && attrInfo && attrInfo.autocast){
switch (attr.type.toLowerCase()) {
// GeoProperties
case 'geo:json':
// FIXME: #1012
// case 'geoproperty':
// case 'point':
// case 'geo:point':
case 'geoproperty':
case 'point':
case 'geo:point':
obj.type = 'geo:json';
obj.value = NGSIUtils.getLngLats('Point', attr.value);
break;
// FIXME: #1012
// case 'linestring':
// case 'geo:linestring':
// obj.type = 'geo:json';
// obj.value = NGSIUtils.getLngLats('LineString', attr.value);
// break;
// case 'polygon':
// case 'geo:polygon':
// obj.type = 'geo:json';
// obj.value = NGSIUtils.getLngLats('Polygon', attr.value);
// break;
// case 'multipoint':
// case 'geo:multipoint':
// obj.type = 'geo:json';
// obj.value = NGSIUtils.getLngLats('MultiPoint', attr.value);
// break;
// case 'multilinestring':
// case 'geo:multilinestring':
// obj.type = 'geo:json';
// obj.value = NGSIUtils.getLngLats('MultiLineString', attr.value);
// break;
// case 'multipolygon':
// case 'geo:multipolygon':
// obj.type = 'geo:json';
// obj.value = NGSIUtils.getLngLats('MultiPolygon', attr.value);
// break;
case 'linestring':
case 'geo:linestring':
obj.type = 'geo:json';
obj.value = NGSIUtils.getLngLats('LineString', attr.value);
break;
case 'polygon':
case 'geo:polygon':
obj.type = 'geo:json';
obj.value = NGSIUtils.getLngLats('Polygon', attr.value);
break;
case 'multipoint':
case 'geo:multipoint':
obj.type = 'geo:json';
obj.value = NGSIUtils.getLngLats('MultiPoint', attr.value);
break;
case 'multilinestring':
case 'geo:multilinestring':
obj.type = 'geo:json';
obj.value = NGSIUtils.getLngLats('MultiLineString', attr.value);
break;
case 'multipolygon':
case 'geo:multipolygon':
obj.type = 'geo:json';
obj.value = NGSIUtils.getLngLats('MultiPolygon', attr.value);
break;
}
} else if ( attr.type.toLowerCase() === 'geo:json'){
// cast GeoJSON by default
obj.type = 'geo:json';
obj.value = NGSIUtils.getLngLats('Point', attr.value);
}
}
return obj;
Expand Down Expand Up @@ -447,7 +454,8 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca

try {
// Format any GeoJSON attrs properly
options.json.entities[entity][att] = formatGeoAttrs(options.json.entities[entity][att]);
const attrInfo = _.findWhere(typeInformation.active, {name: att});
options.json.entities[entity][att] = formatGeoAttrs(options.json.entities[entity][att], attrInfo);
} catch (error) {
return callback(new errors.BadGeocoordinates(JSON.stringify(options.json)));
}
Expand All @@ -469,7 +477,8 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca

try {
// Format any GeoJSON attrs properly
options.json[att] = formatGeoAttrs(options.json[att]);
const attrInfo = _.findWhere(typeInformation.active, {name: att});
options.json[att] = formatGeoAttrs(options.json[att], attrInfo);
} catch (error) {
return callback(new errors.BadGeocoordinates(JSON.stringify(options.json)));
}
Expand Down
5 changes: 5 additions & 0 deletions lib/templates/createDevice.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@
"type": "string",
"pattern": "^([^<>();'=\"]+)+$"
},
"autocast": {
"required": false,
"description": "Whether to autocast this value as a GeoJSON type",
"type": "boolean"
},
"expression": {
"description": "Optional expression for measurement transformation",
"type": "string"
Expand Down
9 changes: 7 additions & 2 deletions lib/templates/createDeviceLax.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@
"type": "string",
"pattern": "^([^<>();'=\"]+)+$"
},
"autocast": {
"required": false,
"description": "Whether to autocast this value as a GeoJSON type",
"type": "boolean"
},
"expression": {
"description": "Optional expression for measurement transformation",
"type": "string",
Expand All @@ -121,7 +126,7 @@
"properties": {
"object_id": {
"description": "ID of the attribute in the device",
"type": "string"
"type": "string"
},
"type": {
"description": "Type of the attribute in the target entity",
Expand Down Expand Up @@ -181,4 +186,4 @@
}
}
}
}
}
5 changes: 5 additions & 0 deletions lib/templates/updateDevice.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@
"type": "string",
"pattern": "^([^<>();'=\"]+)+$"
},
"autocast": {
"required": false,
"description": "Whether to autocast this value as a GeoJSON type",
"type": "boolean"
},
"expression": {
"description": "Optional expression for measurement transformation",
"type": "string"
Expand Down
7 changes: 6 additions & 1 deletion lib/templates/updateDeviceLax.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@
"type": "string",
"pattern": "^([^<>();'=\"]+)+$"
},
"autocast": {
"required": false,
"description": "Whether to autocast this value as a GeoJSON type",
"type": "boolean"
},
"expression": {
"description": "Optional expression for measurement transformation",
"type": "string",
Expand Down Expand Up @@ -154,4 +159,4 @@
"type": "array"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "TheFirstLight",
"type": "TheLightType",
"location": {
"type": "geo:point",
"value": "0, 0"
"type": "geo:point",
"value": "0, 0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"location": {
"value": "23,12.5",
"type": "Point"
}
}

Loading