diff --git a/doc/advanced-topics.md b/doc/advanced-topics.md index 61869a13b..cbd9b165a 100644 --- a/doc/advanced-topics.md +++ b/doc/advanced-topics.md @@ -1,37 +1,36 @@ ## Advanced Topics -* [Secured access to the Context Broker](#secured-access-to-the-context-broker) -* [GeoJSON support NGSI-LD only](#geojson-support-ngsi-ld-only) -* [Metadata support](#metadata-support) - * [NGSI LD data and metadata considerations](#ngsi-ld-data-and-metadata-considerations) -* [NGSI-LD Linked Data support](#ngsi-ld-linked-data-support) -* [Autoprovision configuration (autoprovision)](#autoprovision-configuration-autoprovision) -* [Explicitly defined attributes (explicitAttrs)](#explicitly-defined-attributes-explicitattrs) -* [Configuring operation to persist the data in Context Broker (appendMode)](#configuring-operation-to-persist-the-data-in-context-broker-appendmode) -* [Data mapping plugins](#data-mapping-plugins) - * [Development](#development) - * [Provided plugins](#provided-plugins) - * [Timestamp Compression plugin (compressTimestamp)](#timestamp-compression-plugin-compresstimestamp) - * [Attribute Alias plugin (attributeAlias)](#attribute-alias-plugin-attributealias) - * [Event plugin (addEvents)](#event-plugin-addevents) - * [Timestamp Processing Plugin (timestampProcess)](#timestamp-processing-plugin-timestampprocess) - * [Expression Translation plugin (expressionTransformation)](#expression-translation-plugin-expressiontransformation) - * [Multientity plugin (multiEntity)](#multientity-plugin-multientity) - * [Bidirectionality plugin (bidirectional)](#bidirectionality-plugin-bidirectional) - * [Autoprovision configuration (autoprovision)](#autoprovision-configuration-autoprovision) - * [Explicitly defined attributes (explicitAttrs)](#explicitly-defined-attributes-explicitattrs) - * [Configuring operation to persist the data in Context Broker (appendMode)](#configuring-operation-to-persist-the-data-in-context-broker-appendmode) -* [Old IoTAgent data migration](#old-iotagent-data-migration) - +- [Secured access to the Context Broker](#secured-access-to-the-context-broker) +- [GeoJSON support NGSI-LD only](#geojson-support-ngsi-ld-only) +- [Metadata support](#metadata-support) + - [NGSI LD data and metadata considerations](#ngsi-ld-data-and-metadata-considerations) +- [NGSI-LD Linked Data support](#ngsi-ld-linked-data-support) +- [Autoprovision configuration (autoprovision)](#autoprovision-configuration-autoprovision) +- [Explicitly defined attributes (explicitAttrs)](#explicitly-defined-attributes-explicitattrs) +- [Configuring operation to persist the data in Context Broker (appendMode)](#configuring-operation-to-persist-the-data-in-context-broker-appendmode) +- [Data mapping plugins](#data-mapping-plugins) + - [Development](#development) + - [Provided plugins](#provided-plugins) + - [Timestamp Compression plugin (compressTimestamp)](#timestamp-compression-plugin-compresstimestamp) + - [Attribute Alias plugin (attributeAlias)](#attribute-alias-plugin-attributealias) + - [Event plugin (addEvents)](#event-plugin-addevents) + - [Timestamp Processing Plugin (timestampProcess)](#timestamp-processing-plugin-timestampprocess) + - [Expression Translation plugin (expressionTransformation)](#expression-translation-plugin-expressiontransformation) + - [Multientity plugin (multiEntity)](#multientity-plugin-multientity) + - [Bidirectionality plugin (bidirectional)](#bidirectionality-plugin-bidirectional) + - [Autoprovision configuration (autoprovision)](#autoprovision-configuration-autoprovision) + - [Explicitly defined attributes (explicitAttrs)](#explicitly-defined-attributes-explicitattrs) + - [Configuring operation to persist the data in Context Broker (appendMode)](#configuring-operation-to-persist-the-data-in-context-broker-appendmode) +- [Old IoTAgent data migration](#old-iotagent-data-migration) ### Secured access to the Context Broker For access to instances of the Context Broker secured with a [PEP Proxy](https://github.com/telefonicaid/fiware-orion-pep), an authentication mechanism based in Keystone Trust -tokens is provided. A trust token is a way of Keystone to allow an user delegates a role to another user for a subservice. -It is a long-term token that can be issued by any user to give another user permissions -to impersonate him with a given role in a given project (subservice). Such impersonation itself is in turn based on a short-term -access token. +tokens is provided. A trust token is a way of Keystone to allow an user delegates a role to another user for a +subservice. It is a long-term token that can be issued by any user to give another user permissions to impersonate him +with a given role in a given project (subservice). Such impersonation itself is in turn based on a short-term access +token. For the authentication mechanisms to work, the `authentication` attribute in the configuration has to be fully configured, and the `authentication.enabled` subattribute should have the value `true`. @@ -73,13 +72,13 @@ Agent. Complete info on Keystone trust tokens could be found at: -- [Trusts concept](https://docs.openstack.org/keystone/stein/user/trusts) -- [Trusts API](https://docs.openstack.org/keystone/stein/api_curl_examples.html#post-v3-os-trust-trusts) +- [Trusts concept](https://docs.openstack.org/keystone/stein/user/trusts) +- [Trusts API](https://docs.openstack.org/keystone/stein/api_curl_examples.html#post-v3-os-trust-trusts) ### GeoJSON support NGSI-LD only -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. @@ -132,6 +131,29 @@ 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 +{ + "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 @@ -179,7 +201,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. @@ -269,26 +291,28 @@ 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 -intended to avoid provisioning devices (and for it to be effective, it would have to be provisional). +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 -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 -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. +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 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 -default UPDATE. This have implications in the use of attributes with Context Providers, so this flag should be used with care. +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 default +UPDATE. This have implications in the use of attributes with Context Providers, so this flag should be used with care. ### Data mapping plugins @@ -355,8 +379,8 @@ 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 -it in queries (and viceversa, receive the extended one in queries and return it in updates). +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) @@ -373,8 +397,8 @@ events in the IoT Agent with the configured type name will be marked as events. ##### Timestamp Processing Plugin (timestampProcess) -This plugin processes the entity attributes looking for a `TimeInstant` attribute. If one is found, for NGSIv2, -the plugin adds a `TimeInstant` attribute as metadata for every other attribute in the same request. With NGSI-LD, the +This plugin processes the entity attributes looking for a `TimeInstant` attribute. If one is found, for NGSIv2, the +plugin adds a `TimeInstant` attribute as metadata for every other attribute in the same request. With NGSI-LD, the Standard `observedAt` property-of-a-property is used instead. ##### Expression Translation plugin (expressionTransformation) @@ -438,7 +462,8 @@ When a device is provisioned with bidirectional attributes, the IoTAgent subscri change notification for that attribute arrives to the IoTA, it applies the transformation defined in the device provisioning payload to the notification, and calls the underlying notification handler with the transformed entity. -The following `attributes` section shows an example of the plugin configuration (using IOTA_AUTOCAST=false to avoid translation from geo:point to geo:json) +The following `attributes` section shows an example of the plugin configuration (using IOTA_AUTOCAST=false to avoid +translation from geo:point to geo:json) ```json "attributes": [ diff --git a/doc/api.md b/doc/api.md index 2060487f5..5afb673f7 100644 --- a/doc/api.md +++ b/doc/api.md @@ -75,26 +75,26 @@ information configured: The table below shows the information held in the service group provisioning resource. The table also contains the correspondence between the API resource fields and the same fields in the database model. -| Payload Field | DB Field | Definition | -| ------------------------------ | ------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `service` | `service` | Service of the devices of this type | -| `subservice` | `subservice` | Subservice of the devices of this type. | -| `resource` | `resource` | string representing the Southbound resource that will be used to assign a type to a device (e.g.: pathname in the southbound port). | -| `apikey` | `apikey` | API Key string. | -| `timestamp` | `timestamp` | Optional flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current timestamp. With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. | true | -| `entity_type` | `entity_type` | name of the Entity `type` to assign to the group. | -| `trust` | `trust` | trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). | -| `cbHost` | `cbHost` | Context Broker connection information. This options can be used to override the global ones for specific types of devices. | -| `lazy` | `lazy` | list of common lazy attributes of the device. For each attribute, its `name` and `type` must be provided. | -| `commands` | `commands` | list of common commands attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. | -| `attributes` | `attributes` | list of common active attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. | -| `static_attributes` | `staticAttributes` | this attributes will be added to all the entities of this group 'as is', additional `metadata` is optional. | -| `internal_attributes` | `internalAttributes` | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. | -| `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, `legacy` is used as default value. | -| `explicitAttrs` | `explicitAttrs` | optional boolean value, to support selective ignore of measures so that IOTA doesn’t progress. If not specified default is `false`. | -| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. Possible values are: `v2` or `ld`. The default is `v2`. When not running in mixed mode, this field is ignored. | -| `defaultEntityNameConjunction` | `defaultEntityNameConjunction` | optional string value to set default conjunction string used to compose a default `entity_name` when is not provided at device provisioning time. | -| `autoprovision` | `autoprovision` | optional boolean: If `false`, autoprovisioned devices (i.e. devices that are not created with an explicit provision operation but when the first measure arrives) are not allowed in this group. Default (in the case of omitting the field) is `true`. | +| Payload Field | DB Field | Definition | +| ------------------------------ | ------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `service` | `service` | Service of the devices of this type | +| `subservice` | `subservice` | Subservice of the devices of this type. | +| `resource` | `resource` | string representing the Southbound resource that will be used to assign a type to a device (e.g.: pathname in the southbound port). | +| `apikey` | `apikey` | API Key string. | +| `timestamp` | `timestamp` | Optional flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current timestamp. With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. | true | +| `entity_type` | `entity_type` | name of the Entity `type` to assign to the group. | +| `trust` | `trust` | trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). | +| `cbHost` | `cbHost` | Context Broker connection information. This options can be used to override the global ones for specific types of devices. | +| `lazy` | `lazy` | list of common lazy attributes of the device. For each attribute, its `name` and `type` must be provided. | +| `commands` | `commands` | list of common commands attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. | +| `attributes` | `attributes` | list of common active attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. | +| `static_attributes` | `staticAttributes` | this attributes will be added to all the entities of this group 'as is', additional `metadata` is optional. | +| `internal_attributes` | `internalAttributes` | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. | +| `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, `legacy` is used as default value. | +| `explicitAttrs` | `explicitAttrs` | optional boolean value, to support selective ignore of measures so that IOTA doesn’t progress. If not specified default is `false`. | +| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. Possible values are: `v2` or `ld`. The default is `v2`. When not running in mixed mode, this field is ignored. | +| `defaultEntityNameConjunction` | `defaultEntityNameConjunction` | optional string value to set default conjunction string used to compose a default `entity_name` when is not provided at device provisioning time. | +| `autoprovision` | `autoprovision` | optional boolean: If `false`, autoprovisioned devices (i.e. devices that are not created with an explicit provision operation but when the first measure arrives) are not allowed in this group. Default (in the case of omitting the field) is `true`. | ### Service Group Endpoint @@ -205,45 +205,45 @@ Note that there is a 1:1 correspondence between payload fields and DB fields (bu ### Relationship between service groups and devices -Devices may be associated to exisiting service groups (or not) based in `apiKey` matching or `type` matching (in the case `apiKey` -matching fails). For instance, let's consider a situation in which a service group has been provisioned with `type=X`/`apiKey=111` -and no other service group has been provisioned. +Devices may be associated to exisiting service groups (or not) based in `apiKey` matching or `type` matching (in the +case `apiKey` matching fails). For instance, let's consider a situation in which a service group has been provisioned +with `type=X`/`apiKey=111` and no other service group has been provisioned. -* IoT Agent receives an anonymous measure with `apiKey=111`. The matching `apiKey` means the entity inherits from service group. - Device entity has `type=X` and `apiKey=111` -* IoT Agent receives a provisioning request for an explicit device of `type=Y`/`apiKey=111`. The matching `apiKey` means the entity - inherits from service group but type is overridden. Device entity has `type=Y` and `apiKey=111` -* IoT Agent receives a provisioning request for an explicit device of `type=X`/`apiKey=222`. The matching `type` means the entity - inherits from service group but `apiKey` is overridden. Device entity has `type=X` and `apiKey=222`. -* IoT Agent receives a provisioning request for an explicit device of `type=Y`/`apiKey=222`. No matching. Device entity has `type=Y` - and `apiKey=222` and no service group. +- IoT Agent receives an anonymous measure with `apiKey=111`. The matching `apiKey` means the entity inherits from + service group. Device entity has `type=X` and `apiKey=111` +- IoT Agent receives a provisioning request for an explicit device of `type=Y`/`apiKey=111`. The matching `apiKey` + means the entity inherits from service group but type is overridden. Device entity has `type=Y` and `apiKey=111` +- IoT Agent receives a provisioning request for an explicit device of `type=X`/`apiKey=222`. The matching `type` means + the entity inherits from service group but `apiKey` is overridden. Device entity has `type=X` and `apiKey=222`. +- IoT Agent receives a provisioning request for an explicit device of `type=Y`/`apiKey=222`. No matching. Device + entity has `type=Y` and `apiKey=222` and no service group. ### Device model The table below shows the information held in the Device resource. The table also contains the correspondence between the API resource fields and the same fields in the database model. -| Payload Field | DB Field | Definition | Example of value | -| --------------------- | -------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------- | -| `device_id` | `id` | Device ID that will be used to identify the device. | UO834IO | -| `service` | `service` | Name of the service the device belongs to (will be used in the fiware-service header). | smartGondor | -| `service_path` | `subservice` | Name of the subservice the device belongs to (used in the fiware-servicepath header). | /gardens | -| `entity_name` | `name` | Name of the entity representing the device in the Context Broker | ParkLamplight12 | -| `entity_type` | `type` | Type of the entity in the Context Broker | Lamplights | -| `timezone` | `timezone` | Time zone of the sensor if it has any | America/Santiago | -| `timestamp` | `timestamp` | Optional flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current timestamp. With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. | true | -| `apikey` | `apikey` | Optional Apikey key string to use instead of group apikey | 9n4hb1vpwbjozzmw9f0flf9c2 | -| `endpoint` | `endpoint` | Endpoint where the device is going to receive commands, if any. | http://theDeviceUrl:1234/commands | -| `protocol` | `protocol` | Name of the device protocol, for its use with an IoT Manager. | IoTA-UL | -| `transport` | `transport` | Name of the device transport protocol, for the IoT Agents with multiple transport protocols. | MQTT | -| `attributes` | `active` | List of active attributes of the device | `[ { "name": "attr_name", "type": "Text" } ]` | -| `lazy` | `lazy` | List of lazy attributes of the device | `[ { "name": "attr_name", "type": "Text" } ]` | -| `commands` | `commands` | List of commands of the device | `[ { "name": "attr_name", "type": "Text" } ]` | -| `internal_attributes` | `internalAttributes` | List of internal attributes with free format for specific IoT Agent configuration | LWM2M mappings from object URIs to attributes | -| `static_attributes` | `staticAttributes` | List of static attributes to append to the entity. All the updateContext requests to the CB will have this set of attributes appended. | `[ { "name": "attr_name", "type": "Text" } ]` | -| `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, legacy is used as default value. | -| `explicitAttrs` | `explicitAttrs` | Boolean value to support selective ignore of measures for device so that IOTA doesn’t progress. If not specified default is `false`. | `true/false` | -| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. The default is `v2`. When not running in mixed mode, this field is ignored. | `v2/ld` | +| Payload Field | DB Field | Definition | Example of value | +| --------------------- | -------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------- | +| `device_id` | `id` | Device ID that will be used to identify the device. | UO834IO | +| `service` | `service` | Name of the service the device belongs to (will be used in the fiware-service header). | smartGondor | +| `service_path` | `subservice` | Name of the subservice the device belongs to (used in the fiware-servicepath header). | /gardens | +| `entity_name` | `name` | Name of the entity representing the device in the Context Broker | ParkLamplight12 | +| `entity_type` | `type` | Type of the entity in the Context Broker | Lamplights | +| `timezone` | `timezone` | Time zone of the sensor if it has any | America/Santiago | +| `timestamp` | `timestamp` | Optional flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current timestamp. With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. | true | +| `apikey` | `apikey` | Optional Apikey key string to use instead of group apikey | 9n4hb1vpwbjozzmw9f0flf9c2 | +| `endpoint` | `endpoint` | Endpoint where the device is going to receive commands, if any. | http://theDeviceUrl:1234/commands | +| `protocol` | `protocol` | Name of the device protocol, for its use with an IoT Manager. | IoTA-UL | +| `transport` | `transport` | Name of the device transport protocol, for the IoT Agents with multiple transport protocols. | MQTT | +| `attributes` | `active` | List of active attributes of the device | `[ { "name": "attr_name", "type": "Text" } ]` | +| `lazy` | `lazy` | List of lazy attributes of the device | `[ { "name": "attr_name", "type": "Text" } ]` | +| `commands` | `commands` | List of commands of the device | `[ { "name": "attr_name", "type": "Text" } ]` | +| `internal_attributes` | `internalAttributes` | List of internal attributes with free format for specific IoT Agent configuration | LWM2M mappings from object URIs to attributes | +| `static_attributes` | `staticAttributes` | List of static attributes to append to the entity. All the updateContext requests to the CB will have this set of attributes appended. | `[ { "name": "attr_name", "type": "Text" } ]` | +| `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, legacy is used as default value. | +| `explicitAttrs` | `explicitAttrs` | Boolean value to support selective ignore of measures for device so that IOTA doesn’t progress. If not specified default is `false`. | `true/false` | +| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. The default is `v2`. When not running in mixed mode, this field is ignored. | `v2/ld` | #### Attribute lists diff --git a/doc/architecture.md b/doc/architecture.md index ae259e12e..ff68a2bd1 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -32,7 +32,7 @@ basis preprovisioning the devices). Device measures can have three different beh The following sequence diagram shows the different NGSI interactions an IoT Agent makes with the Context Broker, explained in the following subsections (using the example of a OMA Lightweight M2M device). -![General ](./img/ngsiInteractions.png "NGSI Interactions") +![General ](./img/ngsiInteractions.png 'NGSI Interactions') Be aware that the IoT Agents are only required to support NGSI10 operations `updateContext` and `queryContext` in their standard formats (currently in JSON format; XML deprecated) but will not answer to NGSI9 operations (or NGSI convenience @@ -207,16 +207,16 @@ the concrete IoT Agent implementations will be to map between the native device The following figure offers a graphical example of how a COAP IoT Agent work, ordered from the registration of the device to a command update to the device. -![General ](./img/iotAgentLib.png "Architecture Overview") +![General ](./img/iotAgentLib.png 'Architecture Overview') ### The `TimeInstant` element As part of the device to entity mapping process the IoT Agent creates and updates automatically a special timestamp. This timestamp is represented as two different properties of the mapped entity:: -- With NGSI-v2, an attribute metadata named `TimeInstant` (per dynamic attribute mapped, which captures as an - ISO8601 timestamp when the associated measurement (represented as attribute value) was observed. With NGSI-LD, the - Standard `observedAt` property-of-a-property is used instead. +- With NGSI-v2, an attribute metadata named `TimeInstant` (per dynamic attribute mapped, which captures as an ISO8601 + timestamp when the associated measurement (represented as attribute value) was observed. With NGSI-LD, the Standard + `observedAt` property-of-a-property is used instead. - For NGSI-v2 only, an additional attribute `TimeInstant` is added to the entity which captures as an ISO8601 timestamp when the last measurement received from the device was observed. diff --git a/doc/getting-started.md b/doc/getting-started.md index 692073f72..20fe615be 100644 --- a/doc/getting-started.md +++ b/doc/getting-started.md @@ -10,22 +10,22 @@ custom settings may also be required dependent upon the actual IoT Agent used. ```javascript config = { - logLevel: "DEBUG", + logLevel: 'DEBUG', contextBroker: { - host: "orion", - port: "1026", + host: 'orion', + port: '1026' }, server: { port: 4041, - host: "0.0.0.0", + host: '0.0.0.0' }, deviceRegistry: { - type: "memory", + type: 'memory' }, - service: "openiot", - subservice: "/", - providerUrl: "http://iot-agent:4041", - defaultType: "Thing", + service: 'openiot', + subservice: '/', + providerUrl: 'http://iot-agent:4041', + defaultType: 'Thing' }; ``` diff --git a/doc/howto.md b/doc/howto.md index 13df1ccca..25e345f0e 100644 --- a/doc/howto.md +++ b/doc/howto.md @@ -75,12 +75,12 @@ folder of your project. Remember to change the Context Broker IP to your local C Now we can begin with the code of our IoT Agent. The very minimum code we need to start an IoT Agent is the following: ```javascript -var iotAgentLib = require("iotagent-node-lib"), - config = require("./config"); +var iotAgentLib = require('iotagent-node-lib'), + config = require('./config'); iotAgentLib.activate(config, function (error) { if (error) { - console.log("There was an error activating the IOTA"); + console.log('There was an error activating the IOTA'); process.exit(1); } }); @@ -112,10 +112,10 @@ In order to add the Express dependency to your project, add the following line t The require section would end up like this (the standard `http` module is also needed): ```javascript -var iotAgentLib = require("iotagent-node-lib"), - http = require("http"), - express = require("express"), - config = require("./config"); +var iotAgentLib = require('iotagent-node-lib'), + http = require('http'), + express = require('express'), + config = require('./config'); ``` And install the dependencies as usual with `npm install`. You will have to require both `express` and `http` in your @@ -129,16 +129,16 @@ function initSouthbound(callback) { southboundServer = { server: null, app: express(), - router: express.Router(), + router: express.Router() }; - southboundServer.app.set("port", 8080); - southboundServer.app.set("host", "0.0.0.0"); + southboundServer.app.set('port', 8080); + southboundServer.app.set('host', '0.0.0.0'); - southboundServer.router.get("/iot/d", manageULRequest); + southboundServer.router.get('/iot/d', manageULRequest); southboundServer.server = http.createServer(southboundServer.app); - southboundServer.app.use("/", southboundServer.router); - southboundServer.server.listen(southboundServer.app.get("port"), southboundServer.app.get("host"), callback); + southboundServer.app.use('/', southboundServer.router); + southboundServer.server.listen(southboundServer.app.get('port'), southboundServer.app.get('host'), callback); } ``` @@ -154,18 +154,18 @@ function manageULRequest(req, res, next) { iotAgentLib.retrieveDevice(req.query.i, req.query.k, function (error, device) { if (error) { res.status(404).send({ - message: "Couldn't find the device: " + JSON.stringify(error), + message: "Couldn't find the device: " + JSON.stringify(error) }); } else { values = parseUl(req.query.d, device); - iotAgentLib.update(device.name, device.type, "", values, device, function (error) { + iotAgentLib.update(device.name, device.type, '', values, device, function (error) { if (error) { res.status(500).send({ - message: "Error updating the device", + message: 'Error updating the device' }); } else { res.status(200).send({ - message: "Device successfully updated", + message: 'Device successfully updated' }); } }); @@ -190,17 +190,17 @@ function parseUl(data, device) { } function createAttribute(element) { - var pair = element.split("|"), + var pair = element.split('|'), attribute = { name: pair[0], value: pair[1], - type: findType(pair[0]), + type: findType(pair[0]) }; return attribute; } - return data.split(",").map(createAttribute); + return data.split(',').map(createAttribute); } ``` @@ -227,14 +227,14 @@ show the modifications in the `activate()` function: ```javascript iotAgentLib.activate(config, function (error) { if (error) { - console.log("There was an error activating the IOTA"); + console.log('There was an error activating the IOTA'); process.exit(1); } else { initSouthbound(function (error) { if (error) { - console.log("Could not initialize South bound API due to the following error: %s", error); + console.log('Could not initialize South bound API due to the following error: %s', error); } else { - console.log("Both APIs started successfully"); + console.log('Both APIs started successfully'); } }); } @@ -286,7 +286,7 @@ A HTTP request library will be needed in order to make those calls. To this exte used. In order to do so, add the following require statement to the initialization code: ```javascript -request = require("request"); +request = require('request'); ``` and add the `request` dependency to the `package.json` file: @@ -303,11 +303,11 @@ and add the `request` dependency to the `package.json` file: The require section should now look like this: ```javascript -var iotAgentLib = require("iotagent-node-lib"), - http = require("http"), - express = require("express"), - request = require("request"), - config = require("./config"); +var iotAgentLib = require('iotagent-node-lib'), + http = require('http'), + express = require('express'), + request = require('request'), + config = require('./config'); ``` ### Implementation @@ -321,11 +321,11 @@ for the context provisioning requests. At this point, we should provide two hand ```javascript function queryContextHandler(id, type, service, subservice, attributes, callback) { var options = { - url: "http://127.0.0.1:9999/iot/d", - method: "GET", + url: 'http://127.0.0.1:9999/iot/d', + method: 'GET', qs: { - q: attributes.join(), - }, + q: attributes.join() + } }; request(options, function (error, response, body) { @@ -350,21 +350,21 @@ attributes). Here is the code for the `createResponse()` function: ```javascript function createResponse(id, type, attributes, body) { - var values = body.split(","), + var values = body.split(','), responses = []; for (var i = 0; i < attributes.length; i++) { responses.push({ name: attributes[i], - type: "string", - value: values[i], + type: 'string', + value: values[i] }); } return { id: id, type: type, - attributes: responses, + attributes: responses }; } ``` @@ -374,11 +374,11 @@ function createResponse(id, type, attributes, body) { ```javascript function updateContextHandler(id, type, service, subservice, attributes, callback) { var options = { - url: "http://127.0.0.1:9999/iot/d", - method: "GET", + url: 'http://127.0.0.1:9999/iot/d', + method: 'GET', qs: { - d: createQueryFromAttributes(attributes), - }, + d: createQueryFromAttributes(attributes) + } }; request(options, function (error, response, body) { @@ -388,31 +388,31 @@ function updateContextHandler(id, type, service, subservice, attributes, callbac callback(null, { id: id, type: type, - attributes: attributes, + attributes: attributes }); } }); } ``` -The updateContext handler deals with the modification requests that arrive at the North Port of the IoT Agent via `/v2/op/update`. It is -invoked once for each entity requested (note that a single request can contain multiple entity updates), with the same -parameters used in the queryContext handler. The only difference is the value of the attributes array, now containing a -list of attribute objects, each containing name, type and value. The handler must also make use of the callback to -return a list of updated attributes. +The updateContext handler deals with the modification requests that arrive at the North Port of the IoT Agent via +`/v2/op/update`. It is invoked once for each entity requested (note that a single request can contain multiple entity +updates), with the same parameters used in the queryContext handler. The only difference is the value of the attributes +array, now containing a list of attribute objects, each containing name, type and value. The handler must also make use +of the callback to return a list of updated attributes. For this handler we have used a helper function called `createQueryFromAttributes()`, that transforms the NGSI representation of the attributes to the UL type expected by the device: ```javascript function createQueryFromAttributes(attributes) { - var query = ""; + var query = ''; for (var i in attributes) { - query += attributes[i].name + "|" + attributes[i].value; + query += attributes[i].name + '|' + attributes[i].value; if (i != attributes.length - 1) { - query += ","; + query += ','; } } @@ -555,9 +555,9 @@ variable and afterward the value of the multiCore in the `config.js` file. The r (the standard `http` module is also needed): ```javascript -var iotAgent = require("../lib/iotagent-implementation"), - iotAgentLib = require("iotagent-node-lib"), - config = require("./config"); +var iotAgent = require('../lib/iotagent-implementation'), + iotAgentLib = require('iotagent-node-lib'), + config = require('./config'); ``` It is important to mention the purpose of the `iotAgent` variable. It is the proper implementation of the IoT Agent @@ -573,9 +573,9 @@ about starting the IoTAgent: ```javascript iotAgentLib.startServer(config, iotAgent, function (error) { if (error) { - console.log(context, "Error starting IoT Agent: [%s] Exiting process", error); + console.log(context, 'Error starting IoT Agent: [%s] Exiting process', error); } else { - console.log(context, "IoT Agent started"); + console.log(context, 'IoT Agent started'); } }); ``` @@ -602,7 +602,7 @@ handlers themselves. Here we can see the definition of the configuration handler ```javascript function configurationHandler(configuration, callback) { - console.log("\n\n* REGISTERING A NEW CONFIGURATION:\n%s\n\n", JSON.stringify(configuration, null, 4)); + console.log('\n\n* REGISTERING A NEW CONFIGURATION:\n%s\n\n', JSON.stringify(configuration, null, 4)); callback(null, configuration); } ``` @@ -619,8 +619,8 @@ feature, let's use the provisioning handler to change the value of the type of t ```javascript function provisioningHandler(device, callback) { - console.log("\n\n* REGISTERING A NEW DEVICE:\n%s\n\n", JSON.stringify(device, null, 4)); - device.type = "CertifiedType"; + console.log('\n\n* REGISTERING A NEW DEVICE:\n%s\n\n', JSON.stringify(device, null, 4)); + device.type = 'CertifiedType'; callback(null, device); } ``` diff --git a/doc/installationguide.md b/doc/installationguide.md index 931def7ae..0bffa464f 100644 --- a/doc/installationguide.md +++ b/doc/installationguide.md @@ -56,9 +56,10 @@ allowing the computer to interpret the rest of the data with more clarity and de } ``` -Under mixed mode, **NGSI v2** payloads are used for context broker communications by default, but this payload may also be switched -to **NGSI LD** at service group or device provisioning time using the `ngsiVersion` field in the provisioning API. -The `ngsiVersion` field switch may be added at either group or device level, with the device level overriding the group setting. +Under mixed mode, **NGSI v2** payloads are used for context broker communications by default, but this payload may also +be switched to **NGSI LD** at service group or device provisioning time using the `ngsiVersion` field in the +provisioning API. The `ngsiVersion` field switch may be added at either group or device level, with the device level +overriding the group setting. - **server**: configuration used to create the Context Server (port where the IoT Agent will be listening as a Context Provider and base root to prefix all the paths). The `port` attribute is required. If no `baseRoot` attribute is @@ -159,7 +160,7 @@ used for the same purpose. For instance: ```javascript { - type: "mongodb"; + type: 'mongodb'; } ``` @@ -241,8 +242,8 @@ used for the same purpose. For instance: - **singleConfigurationMode**: enables the Single Configuration mode for backwards compatibility (see description in the Overview). Default to false. - **timestamp**: if this flag is activated: - - For NGSI-v2, the IoT Agent will add a `TimeInstant` metadata attribute to all the attributes updated from - device information. This flag is overwritten by `timestamp` flag in group or device + - For NGSI-v2, the IoT Agent will add a `TimeInstant` metadata attribute to all the attributes updated from device + information. This flag is overwritten by `timestamp` flag in group or device - With NGSI-LD, the standard `observedAt` property-of-a-property is created instead. - **defaultResource**: default string to use as resource for the registration of new Configurations (if no resource is provided). @@ -275,8 +276,8 @@ used for the same purpose. For instance: - **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`. - Default value is `:`. This value is overwritten by `defaultEntityNameConjunction` in group provision. + 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 [forbidden](https://fiware-orion.readthedocs.io/en/master/user/forbidden_characters/index.html) according to the @@ -291,61 +292,61 @@ with container-based technologies, like Docker, Heroku, etc... The following table shows the accepted environment variables, as well as the configuration parameter the variable overrides. -| Environment variable | Configuration attribute | -| :------------------------------- | :------------------------------ | -| IOTA_CB_URL | `contextBroker.url` | -| IOTA_CB_HOST | `contextBroker.host` | -| IOTA_CB_PORT | `contextBroker.port` | -| IOTA_CB_NGSI_VERSION | `contextBroker.ngsiVersion` | -| IOTA_NORTH_HOST | `server.host` | -| IOTA_NORTH_PORT | `server.port` | -| IOTA_PROVIDER_URL | `providerUrl` | -| IOTA_AUTH_ENABLED | `authentication.enabled` | -| IOTA_AUTH_TYPE | `authentication.type` | -| IOTA_AUTH_HEADER | `authentication.header` | -| IOTA_AUTH_URL | `authentication.url` | -| IOTA_AUTH_HOST | `authentication.host` | -| IOTA_AUTH_PORT | `authentication.port` | -| IOTA_AUTH_USER | `authentication.user` | -| IOTA_AUTH_PASSWORD | `authentication.password` | -| IOTA_AUTH_CLIENT_ID | `authentication.clientId` | -| IOTA_AUTH_CLIENT_SECRET | `authentication.clientSecret` | -| IOTA_AUTH_TOKEN_PATH | `authentication.tokenPath` | -| IOTA_AUTH_PERMANENT_TOKEN | `authentication.permanentToken` | -| IOTA_REGISTRY_TYPE | `deviceRegistry.type` | -| IOTA_LOG_LEVEL | `logLevel` | -| IOTA_TIMESTAMP | `timestamp` | -| IOTA_IOTAM_URL | `iotManager.url` | -| IOTA_IOTAM_HOST | `iotManager.host` | -| IOTA_IOTAM_PORT | `iotManager.port` | -| IOTA_IOTAM_PATH | `iotManager.path` | -| IOTA_IOTAM_AGENTPATH | `iotManager.agentPath` | -| IOTA_IOTAM_PROTOCOL | `iotManager.protocol` | -| IOTA_IOTAM_DESCRIPTION | `iotManager.description` | -| IOTA_MONGO_HOST | `mongodb.host` | -| IOTA_MONGO_PORT | `mongodb.port` | -| IOTA_MONGO_DB | `mongodb.db` | -| IOTA_MONGO_REPLICASET | `mongodb.replicaSet` | -| IOTA_MONGO_USER | `mongodb.user` | -| IOTA_MONGO_PASSWORD | `mongodb.password` | -| IOTA_MONGO_AUTH_SOURCE | `mongodb.authSource` | -| IOTA_MONGO_RETRIES | `mongodb.retries` | -| IOTA_MONGO_RETRY_TIME | `mongodb.retryTime` | -| IOTA_MONGO_SSL | `mongodb.ssl` | -| IOTA_MONGO_EXTRAARGS | `mongodb.extraArgs` | -| IOTA_SINGLE_MODE | `singleConfigurationMode` | -| IOTA_APPEND_MODE | `appendMode` | -| IOTA_POLLING_EXPIRATION | `pollingExpiration` | -| IOTA_POLLING_DAEMON_FREQ | `pollingDaemonFrequency` | -| IOTA_AUTOCAST | `autocast` | -| IOTA_MULTI_CORE | `multiCore` | -| IOTA_JSON_LD_CONTEXT | `jsonLdContext` | -| IOTA_FALLBACK_TENANT | `fallbackTenant` | -| IOTA_FALLBACK_PATH | `fallbackPath` | -| IOTA_DEFAULT_EXPRESSION_LANGUAGE | `defaultExpressionLanguage` | -| IOTA_EXPLICIT_ATTRS | `explicitAttrs` | -| IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION | `defaultEntityNameConjunction` | -| IOTA_RELAX_TEMPLATE_VALIDATION | `relaxTemplateValidation` | +| Environment variable | Configuration attribute | +| :----------------------------------- | :------------------------------ | +| IOTA_CB_URL | `contextBroker.url` | +| IOTA_CB_HOST | `contextBroker.host` | +| IOTA_CB_PORT | `contextBroker.port` | +| IOTA_CB_NGSI_VERSION | `contextBroker.ngsiVersion` | +| IOTA_NORTH_HOST | `server.host` | +| IOTA_NORTH_PORT | `server.port` | +| IOTA_PROVIDER_URL | `providerUrl` | +| IOTA_AUTH_ENABLED | `authentication.enabled` | +| IOTA_AUTH_TYPE | `authentication.type` | +| IOTA_AUTH_HEADER | `authentication.header` | +| IOTA_AUTH_URL | `authentication.url` | +| IOTA_AUTH_HOST | `authentication.host` | +| IOTA_AUTH_PORT | `authentication.port` | +| IOTA_AUTH_USER | `authentication.user` | +| IOTA_AUTH_PASSWORD | `authentication.password` | +| IOTA_AUTH_CLIENT_ID | `authentication.clientId` | +| IOTA_AUTH_CLIENT_SECRET | `authentication.clientSecret` | +| IOTA_AUTH_TOKEN_PATH | `authentication.tokenPath` | +| IOTA_AUTH_PERMANENT_TOKEN | `authentication.permanentToken` | +| IOTA_REGISTRY_TYPE | `deviceRegistry.type` | +| IOTA_LOG_LEVEL | `logLevel` | +| IOTA_TIMESTAMP | `timestamp` | +| IOTA_IOTAM_URL | `iotManager.url` | +| IOTA_IOTAM_HOST | `iotManager.host` | +| IOTA_IOTAM_PORT | `iotManager.port` | +| IOTA_IOTAM_PATH | `iotManager.path` | +| IOTA_IOTAM_AGENTPATH | `iotManager.agentPath` | +| IOTA_IOTAM_PROTOCOL | `iotManager.protocol` | +| IOTA_IOTAM_DESCRIPTION | `iotManager.description` | +| IOTA_MONGO_HOST | `mongodb.host` | +| IOTA_MONGO_PORT | `mongodb.port` | +| IOTA_MONGO_DB | `mongodb.db` | +| IOTA_MONGO_REPLICASET | `mongodb.replicaSet` | +| IOTA_MONGO_USER | `mongodb.user` | +| IOTA_MONGO_PASSWORD | `mongodb.password` | +| IOTA_MONGO_AUTH_SOURCE | `mongodb.authSource` | +| IOTA_MONGO_RETRIES | `mongodb.retries` | +| IOTA_MONGO_RETRY_TIME | `mongodb.retryTime` | +| IOTA_MONGO_SSL | `mongodb.ssl` | +| IOTA_MONGO_EXTRAARGS | `mongodb.extraArgs` | +| IOTA_SINGLE_MODE | `singleConfigurationMode` | +| IOTA_APPEND_MODE | `appendMode` | +| IOTA_POLLING_EXPIRATION | `pollingExpiration` | +| IOTA_POLLING_DAEMON_FREQ | `pollingDaemonFrequency` | +| IOTA_AUTOCAST | `autocast` | +| IOTA_MULTI_CORE | `multiCore` | +| IOTA_JSON_LD_CONTEXT | `jsonLdContext` | +| IOTA_FALLBACK_TENANT | `fallbackTenant` | +| IOTA_FALLBACK_PATH | `fallbackPath` | +| IOTA_DEFAULT_EXPRESSION_LANGUAGE | `defaultExpressionLanguage` | +| IOTA_EXPLICIT_ATTRS | `explicitAttrs` | +| IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION | `defaultEntityNameConjunction` | +| IOTA_RELAX_TEMPLATE_VALIDATION | `relaxTemplateValidation` | Note: diff --git a/lib/commonConfig.js b/lib/commonConfig.js index 5dc23c76f..cafcc2af0 100644 --- a/lib/commonConfig.js +++ b/lib/commonConfig.js @@ -243,9 +243,9 @@ function processEnvironmentVariables() { config.contextBroker.jsonLdContext = process.env.IOTA_JSON_LD_CONTEXT.split(',').map((ctx) => ctx.trim()); } - if (Array.isArray(config.contextBroker.jsonLdContext) && config.contextBroker.jsonLdContext.length === 1){ + if (Array.isArray(config.contextBroker.jsonLdContext) && config.contextBroker.jsonLdContext.length === 1) { config.contextBroker.jsonLdContext = config.contextBroker.jsonLdContext[0]; - } + } config.contextBroker.fallbackTenant = process.env.IOTA_FALLBACK_TENANT || config.contextBroker.service || 'iotagent'; @@ -470,7 +470,9 @@ function processEnvironmentVariables() { if (process.env.IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION) { config.defaultEntityNameConjunction = process.env.IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION; } else { - config.defaultEntityNameConjunction = config.defaultEntityNameConjunction ? config.defaultEntityNameConjunction : ':'; + config.defaultEntityNameConjunction = config.defaultEntityNameConjunction + ? config.defaultEntityNameConjunction + : ':'; } } @@ -525,7 +527,6 @@ function ngsiVersion() { return 'unknown'; } - /** * It checks if a combination of typeInformation or common Config is LD * diff --git a/lib/services/devices/deviceService.js b/lib/services/devices/deviceService.js index 78a1ba758..5f03c7ed9 100644 --- a/lib/services/devices/deviceService.js +++ b/lib/services/devices/deviceService.js @@ -214,7 +214,13 @@ function findConfigurationGroup(deviceObj, callback) { } else { config .getGroupRegistry() - .findTypeSilently(deviceObj.service, deviceObj.subservice, deviceObj.type, deviceObj.apikey, handlerGroupFindByType); + .findTypeSilently( + deviceObj.service, + deviceObj.subservice, + deviceObj.type, + deviceObj.apikey, + handlerGroupFindByType + ); } } @@ -581,7 +587,12 @@ function findOrCreate(deviceId, group, callback) { callback(error, device, group); }); } else { - logger.info(context, 'Device %j not provisioned due autoprovision is disabled by its conf %j', newDevice, group); + logger.info( + context, + 'Device %j not provisioned due autoprovision is disabled by its conf %j', + newDevice, + group + ); callback(new errors.DeviceNotFound(deviceId)); } } else { diff --git a/lib/services/devices/devices-NGSI-v2.js b/lib/services/devices/devices-NGSI-v2.js index 191f6a197..a600d2dad 100644 --- a/lib/services/devices/devices-NGSI-v2.js +++ b/lib/services/devices/devices-NGSI-v2.js @@ -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))); } @@ -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))); } diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index e07e82fca..647e2ab9f 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -27,6 +27,7 @@ /* eslint-disable consistent-return */ +const _ = require('underscore'); const request = require('request'); const statsService = require('./../stats/statsRegistry'); const async = require('async'); @@ -49,45 +50,49 @@ 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()) { - // GeoProperties - case 'geo:json': - // FIXME: #1012 - // 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; + if (attr.value && attrInfo && attrInfo.autocast) { + switch (attr.type.toLowerCase()) { + // GeoProperties + case 'geoproperty': + case 'point': + case 'geo:point': + obj.type = 'geo:json'; + obj.value = NGSIUtils.getLngLats('Point', 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; @@ -449,7 +454,11 @@ 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))); } @@ -471,7 +480,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))); } diff --git a/lib/templates/createDevice.json b/lib/templates/createDevice.json index 26fe72e72..0f02d5e4f 100644 --- a/lib/templates/createDevice.json +++ b/lib/templates/createDevice.json @@ -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" diff --git a/lib/templates/createDeviceLax.json b/lib/templates/createDeviceLax.json index 64d25efda..3937bb205 100644 --- a/lib/templates/createDeviceLax.json +++ b/lib/templates/createDeviceLax.json @@ -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", @@ -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", @@ -181,4 +186,4 @@ } } } -} \ No newline at end of file +} diff --git a/lib/templates/updateDevice.json b/lib/templates/updateDevice.json index a8d360a00..4dba99c8c 100644 --- a/lib/templates/updateDevice.json +++ b/lib/templates/updateDevice.json @@ -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" diff --git a/lib/templates/updateDeviceLax.json b/lib/templates/updateDeviceLax.json index 133f6999d..5974c6717 100644 --- a/lib/templates/updateDeviceLax.json +++ b/lib/templates/updateDeviceLax.json @@ -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", @@ -154,4 +159,4 @@ "type": "array" } } -} \ No newline at end of file +} diff --git a/test/unit/general/deviceService-test.js b/test/unit/general/deviceService-test.js index fa72b423c..503983d6a 100644 --- a/test/unit/general/deviceService-test.js +++ b/test/unit/general/deviceService-test.js @@ -187,14 +187,12 @@ describe('NGSI-v2 - Device Service: utils', function () { .post('/v2/registrations') .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' }); - contextBrokerMock .matchHeader('fiware-service', 'testservice') .matchHeader('fiware-servicepath', '/testingPath') .post('/v2/entities?options=upsert') .reply(204); - async.series([request.bind(request, groupCreation), request.bind(request, deviceCreation)], function ( error, results @@ -222,11 +220,10 @@ describe('NGSI-v2 - Device Service: utils', function () { .post('/v2/registrations') .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' }); - contextBrokerMock .matchHeader('fiware-service', 'testservice') .matchHeader('fiware-servicepath', '/testingPath') - .post('/v2/entities?options=upsert') + .post('/v2/entities?options=upsert') .reply(204); async.series([request.bind(request, groupCreation)], function (error, results) { diff --git a/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json b/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json index d29d80454..68f806f38 100644 --- a/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json +++ b/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json @@ -2,7 +2,7 @@ "id": "TheFirstLight", "type": "TheLightType", "location": { - "type": "geo:point", - "value": "0, 0" + "type": "geo:point", + "value": "0, 0" } } diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextGeopropertiesAsString.json b/test/unit/ngsiv2/examples/contextRequests/updateContextGeopropertiesAsString.json new file mode 100644 index 000000000..dfe69c606 --- /dev/null +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextGeopropertiesAsString.json @@ -0,0 +1,7 @@ +{ + "location": { + "value": "23,12.5", + "type": "Point" + } +} + diff --git a/test/unit/ngsiv2/ngsiService/geoproperties-test.js b/test/unit/ngsiv2/ngsiService/geoproperties-test.js index 116f29ba1..e71614d6a 100644 --- a/test/unit/ngsiv2/ngsiService/geoproperties-test.js +++ b/test/unit/ngsiv2/ngsiService/geoproperties-test.js @@ -24,38 +24,65 @@ */ const iotAgentLib = require('../../../../lib/fiware-iotagent-lib'); -// FIXME: #1012 -//const utils = require('../../../tools/utils'); -//const should = require('should'); +const utils = require('../../../tools/utils'); +const should = require('should'); const logger = require('logops'); -//const nock = require('nock'); -//let contextBrokerMock; -//const iotAgentConfig = { -// autocast: true, -// contextBroker: { -// host: '192.168.1.1', -// port: '1026', -// ngsiVersion: 'v2' -// }, -// server: { -// port: 4041 -// }, -// types: { -// Light: { -// commands: [], -// type: 'Light', -// active: [ -// { -// name: 'location', -// type: 'geo:json' -// } -// ] -// } -// }, -// service: 'smartgondor', -// subservice: 'gardens', -// providerUrl: 'http://smartgondor.com' -//}; +const nock = require('nock'); +let contextBrokerMock; +const iotAgentConfig = { + autocast: true, + contextBroker: { + host: '192.168.1.1', + port: '1026', + ngsiVersion: 'v2' + }, + server: { + port: 4041 + }, + types: { + Light: { + commands: [], + type: 'Light', + active: [ + { + name: 'location', + type: 'geo:json' + } + ] + } + }, + service: 'smartGondor', + subservice: 'gardens', + providerUrl: 'http://smartGondor.com' +}; + +// No autoCast +const iotAgentConfig2 = { + autocast: true, + contextBroker: { + host: '192.168.1.1', + port: '1026', + ngsiVersion: 'v2' + }, + server: { + port: 4041 + }, + types: { + Light: { + commands: [], + type: 'Light', + active: [ + { + name: 'location', + type: 'geo:json' + } + ] + } + }, + service: 'smartGondor', + subservice: 'gardens', + providerUrl: 'http://smartGondor.com' +}; describe('NGSI-v2 - Geo-JSON types autocast test', function () { beforeEach(function () { @@ -66,362 +93,406 @@ describe('NGSI-v2 - Geo-JSON types autocast test', function () { iotAgentLib.deactivate(done); }); - // FIXME: #1012 - // describe( - // 'When the IoT Agent receives new geo-information from a device.' + - // 'Location with geo:json type and String value', - // function () { - // const values = [ - // { - // name: 'location', - // type: 'geo:json', - // value: '23,12.5' - // } - // ]; - - // beforeEach(function (done) { - // nock.cleanAll(); - - // contextBrokerMock = nock('http://192.168.1.1:1026') - // .matchHeader('fiware-service', 'smartgondor') - // .post( - // '/v2/entities/light1/attrs', - // utils.readExampleFile( - // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties1.json' - // ) - // ) - // .query({ type: 'Light' }) - // .reply(204); - - // iotAgentLib.activate(iotAgentConfig, done); - // }); - - // it('should change the value of the corresponding attribute in the context broker', function (done) { - // iotAgentLib.update('light1', 'Light', '', values, function (error) { - // should.not.exist(error); - // contextBrokerMock.done(); - // done(); - // }); - // }); - // } - // ); - - // describe( - // 'When the IoT Agent receives new geo-information from a device.' + - // 'Location with geo:json type and GeoJSON object value', - // function () { - // const values = [ - // { - // name: 'location', - // type: 'geo:json', - // value: { - // type: 'Point', - // coordinates: [23, 12.5] - // } - // } - // ]; - - // beforeEach(function (done) { - // nock.cleanAll(); - - // contextBrokerMock = nock('http://192.168.1.1:1026') - // .matchHeader('fiware-service', 'smartgondor') - // .post( - // '/v2/entities/light1/attrs', - // utils.readExampleFile( - // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties1.json' - // ) - // ) - // .query({ type: 'Light' }) - // .reply(204); - - // iotAgentLib.activate(iotAgentConfig, done); - // }); - - // it('should change the value of the corresponding attribute in the context broker', function (done) { - // iotAgentLib.update('light1', 'Light', '', values, function (error) { - // should.not.exist(error); - // contextBrokerMock.done(); - // done(); - // }); - // }); - // } - // ); - - // describe('When the IoT Agent receives new geo-information from a device. Location with Point type and Array value', function () { - // const values = [ - // { - // name: 'location', - // type: 'Point', - // value: [23, 12.5] - // } - // ]; - - // beforeEach(function (done) { - // nock.cleanAll(); - - // contextBrokerMock = nock('http://192.168.1.1:1026') - // .matchHeader('fiware-service', 'smartgondor') - // .post( - // '/v2/entities/light1/attrs', - // utils.readExampleFile( - // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties1.json' - // ) - // ) - // .query({ type: 'Light' }) - // .reply(204); - - // iotAgentLib.activate(iotAgentConfig, done); - // }); - - // it('should change the value of the corresponding attribute in the context broker', function (done) { - // iotAgentLib.update('light1', 'Light', '', values, function (error) { - // should.not.exist(error); - // contextBrokerMock.done(); - // done(); - // }); - // }); - // }); - - // describe( - // 'When the IoT Agent receives new geo-information from a device.' + - // 'Location with LineString type and Array value', - // function () { - // const values = [ - // { - // name: 'location', - // type: 'LineString', - // value: [ - // [23, 12.5], - // [22, 12.5] - // ] - // } - // ]; - - // beforeEach(function (done) { - // nock.cleanAll(); - - // contextBrokerMock = nock('http://192.168.1.1:1026') - // .matchHeader('fiware-service', 'smartgondor') - // .post( - // '/v2/entities/light1/attrs', - // utils.readExampleFile( - // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties2.json' - // ) - // ) - // .query({ type: 'Light' }) - // .reply(204); - - // iotAgentLib.activate(iotAgentConfig, done); - // }); - - // it('should change the value of the corresponding attribute in the context broker', function (done) { - // iotAgentLib.update('light1', 'Light', '', values, function (error) { - // should.not.exist(error); - // contextBrokerMock.done(); - // done(); - // }); - // }); - // } - // ); - - // describe( - // 'When the IoT Agent receives new geo-information from a device.' + - // 'Location with LineString type and Array of Strings', - // function () { - // const values = [ - // { - // name: 'location', - // type: 'LineString', - // value: ['23,12.5', '22,12.5'] - // } - // ]; - - // beforeEach(function (done) { - // nock.cleanAll(); - - // contextBrokerMock = nock('http://192.168.1.1:1026') - // .matchHeader('fiware-service', 'smartgondor') - // .post( - // '/v2/entities/light1/attrs', - // utils.readExampleFile( - // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties2.json' - // ) - // ) - // .query({ type: 'Light' }) - // .reply(204); - - // iotAgentLib.activate(iotAgentConfig, done); - // }); - - // it('should change the value of the corresponding attribute in the context broker', function (done) { - // iotAgentLib.update('light1', 'Light', '', values, function (error) { - // should.not.exist(error); - // contextBrokerMock.done(); - // done(); - // }); - // }); - // } - // ); - - // describe('When the IoT Agent receives new geo-information from a device. Location with None type', function () { - // const values = [ - // { - // name: 'location', - // type: 'None', - // value: 'null' - // } - // ]; - - // beforeEach(function (done) { - // nock.cleanAll(); - - // contextBrokerMock = nock('http://192.168.1.1:1026') - // .matchHeader('fiware-service', 'smartgondor') - // .post( - // '/v2/entities/light1/attrs', - // utils.readExampleFile( - // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties3.json' - // ) - // ) - // .query({ type: 'Light' }) - // .reply(204); - - // iotAgentLib.activate(iotAgentConfig, done); - // }); - - // it('should change the value of the corresponding attribute in the context broker', function (done) { - // iotAgentLib.update('light1', 'Light', '', values, function (error) { - // should.not.exist(error); - // contextBrokerMock.done(); - // done(); - // }); - // }); - // }); - - // describe( - // 'When the IoT Agent receives new geo-information from a device.' + - // 'Location with Polygon type - Array of coordinates', - // function () { - // const values = [ - // { - // name: 'location', - // type: 'Polygon', - // value: [ - // [23, 12.5], - // [22, 13.5], - // [22, 13.5] - // ] - // } - // ]; - - // beforeEach(function (done) { - // nock.cleanAll(); - - // contextBrokerMock = nock('http://192.168.1.1:1026') - // .matchHeader('fiware-service', 'smartgondor') - // .post( - // '/v2/entities/light1/attrs', - // utils.readExampleFile( - // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties4.json' - // ) - // ) - // .query({ type: 'Light' }) - // .reply(204); - - // iotAgentLib.activate(iotAgentConfig, done); - // }); - - // it('should change the value of the corresponding attribute in the context broker', function (done) { - // iotAgentLib.update('light1', 'Light', '', values, function (error) { - // should.not.exist(error); - // contextBrokerMock.done(); - // done(); - // }); - // }); - // } - // ); - - // describe( - // 'When the IoT Agent receives new geo-information from a device.' + - // 'Location with Polygon type - list of coordinates', - // function () { - // const values = [ - // { - // name: 'location', - // type: 'Polygon', - // value: '23,12.5,22,13.5,22,13.5' - // } - // ]; - - // beforeEach(function (done) { - // nock.cleanAll(); - - // contextBrokerMock = nock('http://192.168.1.1:1026') - // .matchHeader('fiware-service', 'smartgondor') - // .post( - // '/v2/entities/light1/attrs', - // utils.readExampleFile( - // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties4.json' - // ) - // ) - // .query({ type: 'Light' }) - // .reply(204); - - // iotAgentLib.activate(iotAgentConfig, done); - // }); - - // it('should change the value of the corresponding attribute in the context broker', function (done) { - // iotAgentLib.update('light1', 'Light', '', values, function (error) { - // should.not.exist(error); - // contextBrokerMock.done(); - // done(); - // }); - // }); - // } - // ); - - // describe('When the IoT Agent receives new geo-information from a device. Location with a missing latitude', function () { - // const values = [ - // { - // name: 'location', - // type: 'Point', - // value: '23,12.5,22,13.5,22' - // } - // ]; - - // beforeEach(function (done) { - // nock.cleanAll(); - // iotAgentLib.activate(iotAgentConfig, done); - // }); - - // it('should throw a BadGeocoordinates Error', function (done) { - // iotAgentLib.update('light1', 'Light', '', values, function (error) { - // should.exist(error); - // done(); - // }); - // }); - // }); - - // describe('When the IoT Agent receives new geo-information from a device. Location invalid coordinates', function () { - // const values = [ - // { - // name: 'location', - // type: 'Point', - // value: '2016-04-30Z' - // } - // ]; - - // beforeEach(function (done) { - // nock.cleanAll(); - // iotAgentLib.activate(iotAgentConfig, done); - // }); - - // it('should throw a BadGeocoordinates Error', function (done) { - // iotAgentLib.update('light1', 'Light', '', values, function (error) { - // should.exist(error); - // done(); - // }); - // }); - // }); + describe( + 'When the IoT Agent receives new geo-information from a device.' + + 'Location with geo:json type and String value', + function () { + const values = [ + { + name: 'location', + type: 'geo:json', + value: '23,12.5' + } + ]; + + beforeEach(function (done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch( + '/v2/entities/light1/attrs', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties1.json' + ) + ) + .query({ type: 'Light' }) + .reply(204); + + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should change the value of the corresponding attribute as a GeoJSON Point in the context broker', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + } + ); + + + describe( + 'When the IoT Agent receives new geo-information from a device.' + + 'Location with Point type and String value and Point is not autocast', + function () { + const values = [ + { + name: 'location', + type: 'Point', + value: '23,12.5' + } + ]; + + beforeEach(function (done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch( + '/v2/entities/light1/attrs', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextRequests/updateContextGeopropertiesAsString.json' + ) + ) + .query({ type: 'Light' }) + .reply(204); + + iotAgentLib.activate(iotAgentConfig2, done); + }); + + it('should change the value of the corresponding attribute as a String in the context broker', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + } + ); + + describe( + 'When the IoT Agent receives new geo-information from a device.' + + 'Location with geo:json type and GeoJSON object value', + function () { + const values = [ + { + name: 'location', + type: 'geo:json', + value: { + type: 'Point', + coordinates: [23, 12.5] + } + } + ]; + + beforeEach(function (done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch( + '/v2/entities/light1/attrs', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties1.json' + ) + ) + .query({ type: 'Light' }) + .reply(204); + iotAgentConfig.types.Light.active[0].autocast = true; + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should change the value of the corresponding attribute as a GeoJSON Point in the context broker', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + } + ); + + describe('When the IoT Agent receives new geo-information from a device. Location with Point type and Array value', function () { + const values = [ + { + name: 'location', + type: 'Point', + value: [23, 12.5] + } + ]; + + beforeEach(function (done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch( + '/v2/entities/light1/attrs', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties1.json' + ) + ) + .query({ type: 'Light' }) + .reply(204); + iotAgentConfig.types.Light.active[0].autocast = true; + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should change the value of the corresponding attribute as a GeoJSON Point in the context broker', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); + + describe( + 'When the IoT Agent receives new geo-information from a device.' + + 'Location with LineString type and Array value', + function () { + const values = [ + { + name: 'location', + type: 'LineString', + value: [ + [23, 12.5], + [22, 12.5] + ] + } + ]; + + beforeEach(function (done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch( + '/v2/entities/light1/attrs', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties2.json' + ) + ) + .query({ type: 'Light' }) + .reply(204); + + iotAgentConfig.types.Light.active[0].autocast = true; + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should change the value of the corresponding attribute as a GeoJSON LineString in the context broker', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + } + ); + + describe( + 'When the IoT Agent receives new geo-information from a device.' + + 'Location with LineString type and Array of Strings', + function () { + const values = [ + { + name: 'location', + type: 'LineString', + value: ['23,12.5', '22,12.5'] + } + ]; + + beforeEach(function (done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch( + '/v2/entities/light1/attrs', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties2.json' + ) + ) + .query({ type: 'Light' }) + .reply(204); + + iotAgentConfig.types.Light.active[0].autocast = 'LineString'; + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should change the value of the corresponding attribute as a GeoJSON LineString in the context broker', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + } + ); + + describe('When the IoT Agent receives new geo-information from a device. Location with None type', function () { + const values = [ + { + name: 'location', + type: 'None', + value: 'null' + } + ]; + + beforeEach(function (done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch( + '/v2/entities/light1/attrs', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties3.json' + ) + ) + .query({ type: 'Light' }) + .reply(204); + iotAgentConfig.types.Light.active[0].autocast = true; + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should change the value of the corresponding attribute as a None type in the context broker', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); + + describe( + 'When the IoT Agent receives new geo-information from a device.' + + 'Location with Polygon type - Array of coordinates', + function () { + const values = [ + { + name: 'location', + type: 'Polygon', + value: [ + [23, 12.5], + [22, 13.5], + [22, 13.5] + ] + } + ]; + + beforeEach(function (done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch( + '/v2/entities/light1/attrs', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties4.json' + ) + ) + .query({ type: 'Light' }) + .reply(204); + + iotAgentConfig.types.Light.active[0].autocast = true; + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should change the value of the corresponding attribute as a GeoJSON Polygon in the context broker', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + } + ); + + describe( + 'When the IoT Agent receives new geo-information from a device.' + + 'Location with Polygon type - list of coordinates', + function () { + const values = [ + { + name: 'location', + type: 'Polygon', + value: '23,12.5,22,13.5,22,13.5' + } + ]; + + beforeEach(function (done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch( + '/v2/entities/light1/attrs', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties4.json' + ) + ) + .query({ type: 'Light' }) + .reply(204); + iotAgentConfig.types.Light.active[0].autocast = true; + iotAgentConfig.types.Light.active[0].autocast = true; + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should change the value of the corresponding attribute as a GeoJSON Polygon in the context broker', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + } + ); + + describe('When the IoT Agent receives new geo-information from a device. Location with a missing latitude', function () { + const values = [ + { + name: 'location', + type: 'Point', + value: '23,12.5,22,13.5,22' + } + ]; + + beforeEach(function (done) { + iotAgentConfig.types.Light.active[0].autocast = true; + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should throw a BadGeocoordinates Error', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.exist(error); + done(); + }); + }); + }); + + describe('When the IoT Agent receives new geo-information from a device. Location invalid coordinates', function () { + const values = [ + { + name: 'location', + type: 'Point', + value: '2016-04-30Z' + } + ]; + + beforeEach(function (done) { + nock.cleanAll(); + iotAgentConfig.types.Light.active[0].autocast = true; + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should throw a BadGeocoordinates Error', function (done) { + iotAgentLib.update('light1', 'Light', '', values, function (error) { + should.exist(error); + done(); + }); + }); + }); });