Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
2 changes: 1 addition & 1 deletion CHANGES_NEXT_RELEASE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- Add: add getTypeSilently for device group and use in device registration to avoid false mongo alarm
- Fix: group command is not provisioned in device when entity_type is different (#1011)
- Hotfix: avoid automatic conversion from geo:xxxx ('xxxx' diferent from 'json') to geo:json (reverts the work done in PR #854)
- Hotfix: avoid automatic conversion from geo:xxxx ('xxxx' diferent from 'json') to geo:json (reverts some of the work done in PR #854)
- Add: use getDeviceSilently in checkDuplicates to avoid raise a false mongo alarm.
- Add: expose getConfigurationSilently to enable retrieve a configuration without raise a false mongo alarm (#1007)
- Add: db uri and options in mongo connection log INFO trace
Expand Down
54 changes: 37 additions & 17 deletions doc/advanced-topics.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,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 NGSIv1 and NGSIv2)
### 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 @@ -69,15 +69,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 @@ -102,6 +103,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 `autocastGeoJSON` 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",
"autocastGeoJSON" : "true"
}
Copy link
Member

Choose a reason for hiding this comment

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

I think the following syntax would be better:

        {
            "name": "observationSpace",
            "type": "Polygon",
            "autocast" : true
        }

So the way in which the autocast is done depends on the type. This way we can extend the functionality in the future to autocast other types. E.g. to autocast to boolean (e.g. "true" -> true, 0 -> false):

        {
            "name": "observationSpaceEnabled",
            "type": "Boolean",
            "autocast" : true
        }

Maybe it would even better to have a autocastType to cover cases in which the NGSI type need to be customized by the user, eg:

        {
            "name": "observationSpace",
            "type": "MyCustomType",
            "autocastType": "Polygon",
            "autocast" : true
        }

The autocastType could be optional and, in the case it is ommited, type is used.

]
}
```

### Metadata support

Both `attributes` and `static_attributes` may be supplied with metadata when provisioning an IoT Agent, so that the
Expand Down Expand Up @@ -149,7 +171,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 @@ -183,12 +205,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 @@ -222,8 +243,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 Down Expand Up @@ -292,7 +312,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 @@ -302,7 +322,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.autocastGeoJSON){
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 @@ -442,7 +449,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 @@ -464,7 +472,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": "^([^<>();'=\"]+)+$"
},
"autocastGeoJSON": {
"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": "^([^<>();'=\"]+)+$"
},
"autocastGeoJSON": {
"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": "^([^<>();'=\"]+)+$"
},
"autocastGeoJSON": {
"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": "^([^<>();'=\"]+)+$"
},
"autocastGeoJSON": {
"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