From 1567aa2446d5739e051ad0a51e30267880f18505 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Mon, 5 Aug 2024 10:20:25 +0200 Subject: [PATCH 01/14] add metadata attributes to jexl ctxt --- lib/services/ngsi/entities-NGSI-v2.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index d8c64a8f9..e54f3ff4f 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -261,7 +261,7 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal * @param {String} token User token to identify against the PEP Proxies (optional). */ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, token, callback) { - //aux function used to builf JEXL context. + //aux functions used to builf JEXL context. //it returns a flat object from an Attr array function reduceAttrToPlainObject(attrs, initObj = {}) { if (attrs !== undefined && Array.isArray(attrs)) { @@ -273,6 +273,22 @@ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, return initObj; } } + //it returns a flat object from an Metadata Object + function reduceMetadataAttrToPlainObject(attrs, initObj = {}) { + if (attrs !== undefined && Array.isArray(attrs)) { + return attrs.reduce((result, item) => { + if (item.metadata !== undefined) { + for (var meta in item.metadata) { + result['metadata.' + item.name + '.' + meta] = item.metadata[meta].value; + } + } + return result; + }, initObj); + } else { + return initObj; + } + } + //Make a clone and overwrite let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(originTypeInformation); @@ -321,6 +337,8 @@ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt); //id type Service and Subservice jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt); + //metadata attributes + jexlctxt = reduceMetadataAttrToPlainObject(typeInformation.active, jexlctxt); logger.debug( context, From fc0b2d2bdc2d57e712da93720944a75ba63571bc Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Mon, 5 Aug 2024 10:28:48 +0200 Subject: [PATCH 02/14] add static attributes metadata to jexl context --- lib/services/ngsi/entities-NGSI-v2.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index e54f3ff4f..e09b98505 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -339,6 +339,8 @@ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt); //metadata attributes jexlctxt = reduceMetadataAttrToPlainObject(typeInformation.active, jexlctxt); + //metadata static attributes + jexlctxt = reduceMetadataAttrToPlainObject(typeInformation.staticAttributes, jexlctxt); logger.debug( context, From 8fc5a65a67116a5a550f50682c51be17296e6db3 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Mon, 5 Aug 2024 10:51:00 +0200 Subject: [PATCH 03/14] update CNR --- CHANGES_NEXT_RELEASE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 854a262e1..989d7880e 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1,3 +1,4 @@ - Fix: service header to use uppercase in case of update and delete (#1528) - Fix: Allow to send to CB batch update for multimeasures for NGSI-LD (#1623) - Add: new JEXL transformations for including into an array keys that have a certain value: valuePicker and valuePickerMulti +- Add: attribute metadata and static attributes metadata added to jexl context (#1630) From 49f85502765ed7b801de68233aef4fe700820b85 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Mon, 5 Aug 2024 11:12:24 +0200 Subject: [PATCH 04/14] add doc --- doc/api.md | 258 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 146 insertions(+), 112 deletions(-) diff --git a/doc/api.md b/doc/api.md index a0c9ddbac..f9c995cf8 100644 --- a/doc/api.md +++ b/doc/api.md @@ -245,10 +245,13 @@ measure name, unless `explicitAttrs` is defined. Measures `id` or `type` names a ## Device autoprovision and entity creation For those agents that uses IoTA Node LIB version 3.4.0 or higher, you should consider that the entity is not created -automatically when a device is created. This means that all entities into the Context Broker are created when data -arrives from a device, no matter if the device is explicitly provisioned (via [device provisioning API](#create-device-post-iotdevices)) or autoprovisioned. +automatically when a device is created. This means that all entities into the Context Broker are created when data +arrives from a device, no matter if the device is explicitly provisioned (via +[device provisioning API](#create-device-post-iotdevices)) or autoprovisioned. -If for any reason you need the entity at CB before the first measure of the corresponding device arrives to the IOTAgent, you can create it in advance using the Context Broker [NGSI v2 API](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md). +If for any reason you need the entity at CB before the first measure of the corresponding device arrives to the +IOTAgent, you can create it in advance using the Context Broker +[NGSI v2 API](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md). ## Multientity support @@ -479,7 +482,8 @@ mappings of the provision. If `explicitAttrs` is provided both at device and con precedence. Additionally `explicitAttrs` can be used to define which measures (identified by their attribute names, not by their object_id) defined in JSON/JEXL array will be propagated to NGSI interface. -Note that when `explicitAttrs` is an array or a JEXL expression resulting in to Array, if this array is empty then `TimeInstant` is not propaged to CB. +Note that when `explicitAttrs` is an array or a JEXL expression resulting in to Array, if this array is empty then +`TimeInstant` is not propaged to CB. The different possibilities are summarized below: @@ -588,6 +592,10 @@ expression. In all cases the following data is available to all expressions: Additionally, for attribute expressions (`expression`, `entity_name`), `entityNameExp` and metadata expressions (`expression`) measures are available in the **context** used to evaluate them. +Attribute metadata and Static Attribute metadata are available in the **context** under the following convention: +`metadata.AttributeName.MetadataName` or `metadata.StaticAttributeName.MetadataName` in a similar way of defined for +[Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support) + ### Examples of JEXL expressions The following table shows expressions and their expected outcomes taking into account the following measures at @@ -632,53 +640,52 @@ to incorporate new transformations from the IoT Agent configuration file in a fa Current common transformation set: -| JEXL Transformation | Equivalent JavaScript Function | -| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | -| jsonparse: (str) | `JSON.parse(str);` | -| jsonstringify: (obj) | `JSON.stringify(obj);` | -| indexOf: (val, char) | `String(val).indexOf(char);` | -| length: (val) | `String(val).length;` | -| trim: (val) | `String(val).trim();` | -| substr: (val, int1, int2) | `String(val).substr(int1, int2);` | -| addreduce: (arr) | arr.reduce((i, v) | i + v)); | -| lengtharray: (arr) | `arr.length;` | -| typeof: (val) | `typeof val;` | -| isarray: (arr) | `Array.isArray(arr);` | -| isnan: (val) | `isNaN(val);` | -| parseint: (val) | `parseInt(val);` | -| parsefloat: (val) | `parseFloat(val);` | -| toisodate: (val) | `new Date(val).toISOString();` | -| timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` | -| tostring: (val) | `val.toString();` | -| urlencode: (val) | `encodeURI(val);` | -| urldecode: (val) | `decodeURI(val);` | -| replacestr: (str, from, to) | `str.replace(from, to);` | -| replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` | -| replaceallstr: (str, from, to) | `str.replaceAll(from, to);` | -| replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` | -| split: (str, ch) | `str.split(ch);` | -| joinarrtostr: (arr, ch) | `arr.join(ch);` | -| concatarr: (arr, arr2) | `arr.concat(arr2);` | -| mapper: (val, values, choices) | choices[values.findIndex((target) | target == val)]); | -| thmapper: (val, values, choices) | choices[values.reduce((acc,curr,i,arr) | (acc==0)||acc?acc:val<=curr?acc=i:acc=null,null)]; | -| bitwisemask: (i,mask,op,shf) | (op==="&"?parseInt(i)&mask: op==="|"?parseInt(i)|mask: op==="^"?parseInt(i)^mask:i)>>shf; | -| slice: (arr, init, end) | `arr.slice(init,end);` | -| addset: (arr, x) | { return Array.from((new Set(arr)).add(x)) } | -| removeset: (arr, x) | { let s = new Set(arr); s.delete(x); return Array.from(s) } | -| touppercase: (val) | `String(val).toUpperCase()` | -| tolowercase: (val) | `String(val).toLowerCase()` | -| round: (val) | `Math.round(val)` | -| floor: (val) | `Math.floor(val)` | -| ceil: (val) | `Math.ceil(val)` | -| tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` | -| gettime: (d) | `new Date(d).getTime()` | -| toisostring: (d) | `new Date(d).toISOString()` | -| localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` | -| now: () | `Date.now()` | -| hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` | -| valuePicker: (val,pick) | valuePicker: (val,pick) => Object.entries(val).filter(([_, v]) => v === pick).map(([k, _]) => k) | -| valuePickerMulti: (val,pick) | valuePickerMulti: (val,pick) => Object.entries(val).filter(([_, v]) => pick.includes(v)).map(([k, _]) => k) | - +| JEXL Transformation | Equivalent JavaScript Function | +| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | +| jsonparse: (str) | `JSON.parse(str);` | +| jsonstringify: (obj) | `JSON.stringify(obj);` | +| indexOf: (val, char) | `String(val).indexOf(char);` | +| length: (val) | `String(val).length;` | +| trim: (val) | `String(val).trim();` | +| substr: (val, int1, int2) | `String(val).substr(int1, int2);` | +| addreduce: (arr) | arr.reduce((i, v) | i + v)); | +| lengtharray: (arr) | `arr.length;` | +| typeof: (val) | `typeof val;` | +| isarray: (arr) | `Array.isArray(arr);` | +| isnan: (val) | `isNaN(val);` | +| parseint: (val) | `parseInt(val);` | +| parsefloat: (val) | `parseFloat(val);` | +| toisodate: (val) | `new Date(val).toISOString();` | +| timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` | +| tostring: (val) | `val.toString();` | +| urlencode: (val) | `encodeURI(val);` | +| urldecode: (val) | `decodeURI(val);` | +| replacestr: (str, from, to) | `str.replace(from, to);` | +| replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` | +| replaceallstr: (str, from, to) | `str.replaceAll(from, to);` | +| replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` | +| split: (str, ch) | `str.split(ch);` | +| joinarrtostr: (arr, ch) | `arr.join(ch);` | +| concatarr: (arr, arr2) | `arr.concat(arr2);` | +| mapper: (val, values, choices) | choices[values.findIndex((target) | target == val)]); | +| thmapper: (val, values, choices) | choices[values.reduce((acc,curr,i,arr) | (acc==0)||acc?acc:val<=curr?acc=i:acc=null,null)]; | +| bitwisemask: (i,mask,op,shf) | (op==="&"?parseInt(i)&mask: op==="|"?parseInt(i)|mask: op==="^"?parseInt(i)^mask:i)>>shf; | +| slice: (arr, init, end) | `arr.slice(init,end);` | +| addset: (arr, x) | { return Array.from((new Set(arr)).add(x)) } | +| removeset: (arr, x) | { let s = new Set(arr); s.delete(x); return Array.from(s) } | +| touppercase: (val) | `String(val).toUpperCase()` | +| tolowercase: (val) | `String(val).toLowerCase()` | +| round: (val) | `Math.round(val)` | +| floor: (val) | `Math.floor(val)` | +| ceil: (val) | `Math.ceil(val)` | +| tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` | +| gettime: (d) | `new Date(d).getTime()` | +| toisostring: (d) | `new Date(d).toISOString()` | +| localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` | +| now: () | `Date.now()` | +| hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` | +| valuePicker: (val,pick) | valuePicker: (val,pick) => Object.entries(val).filter(([_, v]) => v === pick).map(([k, _]) => k) | +| valuePickerMulti: (val,pick) | valuePickerMulti: (val,pick) => Object.entries(val).filter(([_, v]) => pick.includes(v)).map(([k, _]) => k) | You have available this [JEXL interactive playground][99] with all the transformations already loaded, in which you can test all the functions described above. @@ -1193,13 +1200,20 @@ In this case a batch update (`POST /v2/op/update`) to CB will be generated with ## Command execution -This section reviews the end-to-end process to trigger and receive commands into devices. The URL paths and messages format is based on the [IoT Agent JSON](https://github.com/telefonicaid/iotagent-json). It may differ in the case of using any other IoT Agent. In that case, please refer to the specific IoTA documentation. +This section reviews the end-to-end process to trigger and receive commands into devices. The URL paths and messages +format is based on the [IoT Agent JSON](https://github.com/telefonicaid/iotagent-json). It may differ in the case of +using any other IoT Agent. In that case, please refer to the specific IoTA documentation. ### Triggering commands -This starts the process of sending data to devices. It starts by updating an attribute into the Context Broker defined as `command` in the [config group](#config-group-datamodel) or in the [device provision](#device-datamodel). Commands attributes are created using `command` as attribute type. Also, you can define the protocol you want the commands to be sent (HTTP/MQTT) with the `transport` parameter at the provisioning process. +This starts the process of sending data to devices. It starts by updating an attribute into the Context Broker defined +as `command` in the [config group](#config-group-datamodel) or in the [device provision](#device-datamodel). Commands +attributes are created using `command` as attribute type. Also, you can define the protocol you want the commands to be +sent (HTTP/MQTT) with the `transport` parameter at the provisioning process. -For a given device provisioned with a `ping` command defined, any update on this attribute "ping" at the NGSI entity in the Context Broker will send a command to your device. For instance, to send the `ping` command with value `Ping request` you could use the following operation in the Context Broker API: +For a given device provisioned with a `ping` command defined, any update on this attribute "ping" at the NGSI entity in +the Context Broker will send a command to your device. For instance, to send the `ping` command with value +`Ping request` you could use the following operation in the Context Broker API: ``` PUT /v2/entities//attrs/ping?type= @@ -1213,30 +1227,36 @@ PUT /v2/entities//attrs/ping?type= It is important to note that parameter `type`, with the entity type must be included. -Context Broker API is quite flexible and allows to update an attribute in several ways. Please have a look to the [Orion API]([http://telefonicaid.github.io/fiware-orion/api/v2/stable](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md)) for details. +Context Broker API is quite flexible and allows to update an attribute in several ways. Please have a look to the +[Orion API](<[http://telefonicaid.github.io/fiware-orion/api/v2/stable](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md)>) +for details. -**Important note**: don't use operations in the NGSI API with creation semantics. Otherwise, the entity/attribute will be created locally to Context Broker and the command will not progress to the device (and you will need to delete the created entity/attribute if you want to make it to work again). Thus, the following operations *must not* be used: +**Important note**: don't use operations in the NGSI API with creation semantics. Otherwise, the entity/attribute will +be created locally to Context Broker and the command will not progress to the device (and you will need to delete the +created entity/attribute if you want to make it to work again). Thus, the following operations _must not_ be used: -* `POST /v2/entities` -* `POST /v2/entities//attrs` -* `PUT /v2/entities//attrs` -* `POST /v2/op/entites` with `actionType` `append`, `appendStrict` or `replace` +- `POST /v2/entities` +- `POST /v2/entities//attrs` +- `PUT /v2/entities//attrs` +- `POST /v2/op/entites` with `actionType` `append`, `appendStrict` or `replace` ### Command reception -Once the command is triggered, it is send to the device. Depending on transport protocol, it is going to be sent to the device in a different way. After sending the command, the IoT Agent will have updated the value of `ping_status` to `PENDING` for entity into the Context Broker. Neither -`ping_info` nor `ping` will be updated. +Once the command is triggered, it is send to the device. Depending on transport protocol, it is going to be sent to the +device in a different way. After sending the command, the IoT Agent will have updated the value of `ping_status` to +`PENDING` for entity into the Context Broker. Neither `ping_info` nor `ping` will be updated. #### HTTP devices **Push commands** -Push commands are those that are sent to the device once the IoT Agent receives the request from the Context Broker. In order to -send push commands it is needed to set the `"endpoint": "http://[DEVICE_IP]:[PORT]/"` in the device or group provision. The device -is supposed to be listening for commands at that URL in a synchronous way. Make sure the device endpoint is reachable by the IoT -Agent. Push commands are only valid for HTTP devices. For MQTT devices it is not needed to set the `endpoint` parameter. +Push commands are those that are sent to the device once the IoT Agent receives the request from the Context Broker. In +order to send push commands it is needed to set the `"endpoint": "http://[DEVICE_IP]:[PORT]/"` in the device or group +provision. The device is supposed to be listening for commands at that URL in a synchronous way. Make sure the device +endpoint is reachable by the IoT Agent. Push commands are only valid for HTTP devices. For MQTT devices it is not needed +to set the `endpoint` parameter. -Considering using the IoTA-JSON Agent, and given the previous example, the device should receive a POST request to +Considering using the IoTA-JSON Agent, and given the previous example, the device should receive a POST request to `http://[DEVICE_IP]:[PORT]` with the following payload: ``` @@ -1248,22 +1268,29 @@ Content-Type: application/json **Poll commands** -Poll commands are those that are stored in the IoT Agent waiting to be retrieved by the devices. This kind of -commands are typically used for devices that doesn't have a public IP or the IP cannot be reached because of -power or netkork constrictions. The device connects to the IoT Agent periodically to retrieve commands. In order -to configure the device as poll commands you just need to avoid the usage of `endpoint` parameter in the device provision. +Poll commands are those that are stored in the IoT Agent waiting to be retrieved by the devices. This kind of commands +are typically used for devices that doesn't have a public IP or the IP cannot be reached because of power or netkork +constrictions. The device connects to the IoT Agent periodically to retrieve commands. In order to configure the device +as poll commands you just need to avoid the usage of `endpoint` parameter in the device provision. -Once the command request is issued to the IoT agent, the command is stored waiting to be retrieved by the device. In that moment, the status of the command is `"_status": "PENDING"`. +Once the command request is issued to the IoT agent, the command is stored waiting to be retrieved by the device. In +that moment, the status of the command is `"_status": "PENDING"`. -For HTTP devices, in order to retrieve a poll command, the need to make a GET request to the IoT Agent to the path used as `resource` in the provisioned group (`/iot/json` by default in IoTA-JSON if no `resource` is used) with the following parameters: +For HTTP devices, in order to retrieve a poll command, the need to make a GET request to the IoT Agent to the path used +as `resource` in the provisioned group (`/iot/json` by default in IoTA-JSON if no `resource` is used) with the following +parameters: -**FIXME [#1524](https://github.com/telefonicaid/iotagent-node-lib/issues/1524)**: `resource` different to the default one (`/iot/json` in the case of the [IoTA-JSON](https://github.com/telefonicaid/iotagent-json)) is not working at the present moment, but it will when this issue gets solved. +**FIXME [#1524](https://github.com/telefonicaid/iotagent-node-lib/issues/1524)**: `resource` different to the default +one (`/iot/json` in the case of the [IoTA-JSON](https://github.com/telefonicaid/iotagent-json)) is not working at the +present moment, but it will when this issue gets solved. -* `k`: API key of the device. -* `i`: Device ID. -* `getCmd`: This parameter is used to indicate the IoT Agent that the device is requesting a command. It is needed to set it to `1` +- `k`: API key of the device. +- `i`: Device ID. +- `getCmd`: This parameter is used to indicate the IoT Agent that the device is requesting a command. It is needed to + set it to `1` -Taking the previous example, and considering the usage of the IoTA-JSON Agent, the device should make the following request, being the response to this request a JSON object with the command name as key and the command value as value: +Taking the previous example, and considering the usage of the IoTA-JSON Agent, the device should make the following +request, being the response to this request a JSON object with the command name as key and the command value as value: **Request:** @@ -1276,15 +1303,20 @@ Accept: application/json **Response:**: ``` -200 OK -Content-type: application/json +200 OK +Content-type: application/json -{"ping":"Ping request"} +{"ping":"Ping request"} ``` -For IoT Agents different from IoTA-JSON it is exactly the same just changing in the request the resource by the corresponding resource employed by the agent (i.e., IoTA-UL uses `/iot/d` as default resource instead of `/iot/json`) and setting the correct `` and ``. The response will be also different depending on the IoT Agent employed. +For IoT Agents different from IoTA-JSON it is exactly the same just changing in the request the resource by the +corresponding resource employed by the agent (i.e., IoTA-UL uses `/iot/d` as default resource instead of `/iot/json`) +and setting the correct `` and ``. The response will be also different depending on the IoT Agent +employed. -**FIXME [#1524](https://github.com/telefonicaid/iotagent-node-lib/issues/1524)**: `resource` different to the default one (`/iot/json` in the case of the [IoTA-JSON](https://github.com/telefonicaid/iotagent-json)) is not working at the present moment, but it will when this issue gets solved. +**FIXME [#1524](https://github.com/telefonicaid/iotagent-node-lib/issues/1524)**: `resource` different to the default +one (`/iot/json` in the case of the [IoTA-JSON](https://github.com/telefonicaid/iotagent-json)) is not working at the +present moment, but it will when this issue gets solved. **Request** @@ -1298,61 +1330,61 @@ Content-Type: application/json **Response** ``` -200 OK -Content-type: application/json +200 OK +Content-type: application/json {"ping":"Ping request"} ``` +This is also possible for IoTA-UL Agent changing in the request the resource, setting the correct ``, +``, payload and headers. -This is also possible for IoTA-UL Agent changing in the request the resource, setting the correct ``, ``, payload and headers. - -Once the command is retrieved by the device the status is updated to `"_status": "DELIVERED"`. Note that status `DELIVERED` only make sense in the case of poll commands. In the case of push command it cannot happen. - +Once the command is retrieved by the device the status is updated to `"_status": "DELIVERED"`. Note that status +`DELIVERED` only make sense in the case of poll commands. In the case of push command it cannot happen. #### MQTT devices -For MQTT devices, it is not needed to declare an endpoint (i.e. if included in the provisioning request, it is not used). The device -is supposed to be subscribed to the following MQTT topic where the IoT Agent will publish the command: +For MQTT devices, it is not needed to declare an endpoint (i.e. if included in the provisioning request, it is not +used). The device is supposed to be subscribed to the following MQTT topic where the IoT Agent will publish the command: ``` ///cmd ``` -In the case of using the IoTA-JSON Agent, the device should subscribe to the previous topic where it is going to receive a message like -the following one when a command is triggered in the Context Broker like the previous step: +In the case of using the IoTA-JSON Agent, the device should subscribe to the previous topic where it is going to receive +a message like the following one when a command is triggered in the Context Broker like the previous step: ```json -{"ping":"Ping request"} +{ "ping": "Ping request" } ``` -Please note that the device should subscribe to the broker using the disabled clean session mode (enabled using -`--disable-clean-session` option CLI parameter in `mosquitto_sub`). This option means that all of the subscriptions for the device will -be maintained after it disconnects, along with subsequent QoS 1 and QoS 2 commands that arrive. When the device reconnects, it will -receive all of the queued commands. +Please note that the device should subscribe to the broker using the disabled clean session mode (enabled using +`--disable-clean-session` option CLI parameter in `mosquitto_sub`). This option means that all of the subscriptions for +the device will be maintained after it disconnects, along with subsequent QoS 1 and QoS 2 commands that arrive. When the +device reconnects, it will receive all of the queued commands. ### Command confirmation -Once the command is completely processed by the device, it should return the result of the command to the IoT -Agent. This result will be progressed to the Context Broker where it will be stored in the `_info` -attribute. The status of the command will be stored in the `_status` attribute (`OK` if everything -goes right). +Once the command is completely processed by the device, it should return the result of the command to the IoT Agent. +This result will be progressed to the Context Broker where it will be stored in the `_info` attribute. The +status of the command will be stored in the `_status` attribute (`OK` if everything goes right). -For the IoTA-JSON, the payload of the confirmation message must be a JSON object with name of the command as key -and the result of the command as value. For other IoT Agents, the payload must follow the corresponding protocol. -For a given `ping` command, with a command result `status_ok`, the response payload should be: +For the IoTA-JSON, the payload of the confirmation message must be a JSON object with name of the command as key and the +result of the command as value. For other IoT Agents, the payload must follow the corresponding protocol. For a given +`ping` command, with a command result `status_ok`, the response payload should be: ```JSON {"ping":"status_ok"} ``` -Eventually, once the device makes the response request the IoTA would update the attributes `ping_status` to -`OK` and `ping_info` to `status_ok` for the previous example. +Eventually, once the device makes the response request the IoTA would update the attributes `ping_status` to `OK` and +`ping_info` to `status_ok` for the previous example. #### HTTP -In order confirm the command execution, the device must make a POST request to the IoT Agent with the result -of the command as payload, no matter if it is a push or a poll command. Following with the IoTAgent JSON case, the request must be made to the `/iot/json/commands`, this way: +In order confirm the command execution, the device must make a POST request to the IoT Agent with the result of the +command as payload, no matter if it is a push or a poll command. Following with the IoTAgent JSON case, the request must +be made to the `/iot/json/commands`, this way: ``` POST /iot/json/commands?k=&i= @@ -1364,14 +1396,16 @@ Accept: application/json #### MQTT -The device should publish the result of the command (`{"ping":"status_ok"}` in the previous example) to a -topic following the next pattern: +The device should publish the result of the command (`{"ping":"status_ok"}` in the previous example) to a topic +following the next pattern: ``` ////cmdexe ``` -The IoTA is subscribed to that topic, so it gets the result of the command. When this happens, the status is updated to`"_status": "OK"`. Also the result of the command delivered by the device is stored in the `_info` attribute. +The IoTA is subscribed to that topic, so it gets the result of the command. When this happens, the status is updated +to`"_status": "OK"`. Also the result of the command delivered by the device is stored in the `_info` +attribute. ## Overriding global Context Broker host From c8778c79c495b3c4236a7fbc5f7443eb86638f51 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Mon, 5 Aug 2024 13:36:21 +0200 Subject: [PATCH 05/14] add test --- test/functional/testCases.js | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/test/functional/testCases.js b/test/functional/testCases.js index bb3f51bec..d389ed03b 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -1828,6 +1828,77 @@ const testCases = [ } ] }, + { + describeName: + '0175 - Simple group with active attribute + JEXL expression referencing metadata context attributes', + skip: 'lib', // Explanation in #1523 + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + static_attributes: [ + { + name: 'st_attr', + type: 'Number', + value: 1.5, + metadata: { + coef: { + value: 0.8, + type: 'Float' + } + } + } + ], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Number', + expression: 'a*st_attr*metadata.st_attr.coef' + } + ] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a value (number) through http IT should apply the expression using the context attributes value and send to Context Broker the value "39.60" ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 33 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 39.6, + type: 'Number' + } + } + } + ] + }, { describeName: '0180 - Simple group with active attributes + JEXL multiples expressions at same time', skip: 'lib', // Explanation in #1523 From a1063a18eed665e7489947a28c4960f82f49c16e Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 6 Aug 2024 11:05:52 +0200 Subject: [PATCH 06/14] relace metadata format for jexl context from metadata.Attr.value to metadata_Attr_value --- doc/api.md | 2 +- lib/services/ngsi/entities-NGSI-v2.js | 2 +- test/functional/testCases.js | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/api.md b/doc/api.md index f9c995cf8..44937e0d5 100644 --- a/doc/api.md +++ b/doc/api.md @@ -593,7 +593,7 @@ Additionally, for attribute expressions (`expression`, `entity_name`), `entityNa (`expression`) measures are available in the **context** used to evaluate them. Attribute metadata and Static Attribute metadata are available in the **context** under the following convention: -`metadata.AttributeName.MetadataName` or `metadata.StaticAttributeName.MetadataName` in a similar way of defined for +`metadata_AttributeName_MetadataName` or `metadata_StaticAttributeName_MetadataName` in a similar way of defined for [Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support) ### Examples of JEXL expressions diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index e09b98505..45c035a43 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -279,7 +279,7 @@ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, return attrs.reduce((result, item) => { if (item.metadata !== undefined) { for (var meta in item.metadata) { - result['metadata.' + item.name + '.' + meta] = item.metadata[meta].value; + result['metadata_' + item.name + '_' + meta] = item.metadata[meta].value; } } return result; diff --git a/test/functional/testCases.js b/test/functional/testCases.js index d389ed03b..90e9809d5 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -1831,7 +1831,6 @@ const testCases = [ { describeName: '0175 - Simple group with active attribute + JEXL expression referencing metadata context attributes', - skip: 'lib', // Explanation in #1523 provision: { url: 'http://localhost:' + config.iota.server.port + '/iot/services', method: 'POST', @@ -1861,9 +1860,10 @@ const testCases = [ object_id: 'a', name: 'attr_a', type: 'Number', - expression: 'a*st_attr*metadata.st_attr.coef' + expression: 'a*st_attr*metadata_st_attr_coef' } - ] + ], + explicitAttrs: "['attr_a']" } ] }, From 3120d0a87b551ccdf3965ea34296cf200650b81f Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 6 Aug 2024 11:07:44 +0200 Subject: [PATCH 07/14] update doc --- doc/api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api.md b/doc/api.md index 44937e0d5..07c9384e8 100644 --- a/doc/api.md +++ b/doc/api.md @@ -593,8 +593,8 @@ Additionally, for attribute expressions (`expression`, `entity_name`), `entityNa (`expression`) measures are available in the **context** used to evaluate them. Attribute metadata and Static Attribute metadata are available in the **context** under the following convention: -`metadata_AttributeName_MetadataName` or `metadata_StaticAttributeName_MetadataName` in a similar way of defined for -[Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support) +`metadata__` or `metadata__` in a similar way of defined +for [Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support) ### Examples of JEXL expressions From 45d95381295626ac98fbfa1c12f8e588100a380f Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 6 Aug 2024 11:26:48 +0200 Subject: [PATCH 08/14] ensure metadata is an obj into jexl context --- doc/api.md | 2 +- lib/services/ngsi/entities-NGSI-v2.js | 4 +++- test/functional/testCases.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/api.md b/doc/api.md index 07c9384e8..963336b1c 100644 --- a/doc/api.md +++ b/doc/api.md @@ -593,7 +593,7 @@ Additionally, for attribute expressions (`expression`, `entity_name`), `entityNa (`expression`) measures are available in the **context** used to evaluate them. Attribute metadata and Static Attribute metadata are available in the **context** under the following convention: -`metadata__` or `metadata__` in a similar way of defined +`metadata..` or `metadata..` in a similar way of defined for [Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support) ### Examples of JEXL expressions diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index 45c035a43..22217bf9e 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -277,9 +277,11 @@ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, function reduceMetadataAttrToPlainObject(attrs, initObj = {}) { if (attrs !== undefined && Array.isArray(attrs)) { return attrs.reduce((result, item) => { + result['metadata'] = {}; if (item.metadata !== undefined) { + result['metadata'][item.name] = {}; for (var meta in item.metadata) { - result['metadata_' + item.name + '_' + meta] = item.metadata[meta].value; + result['metadata'][item.name][meta] = item.metadata[meta].value; } } return result; diff --git a/test/functional/testCases.js b/test/functional/testCases.js index 90e9809d5..44431d996 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -1860,7 +1860,7 @@ const testCases = [ object_id: 'a', name: 'attr_a', type: 'Number', - expression: 'a*st_attr*metadata_st_attr_coef' + expression: 'a*st_attr*metadata.st_attr.coef' } ], explicitAttrs: "['attr_a']" From 3036adece9ab86c879b80cbf5c59cc3c4972c08b Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 6 Aug 2024 11:35:52 +0200 Subject: [PATCH 09/14] fix metadata obj into jexl context --- lib/services/ngsi/entities-NGSI-v2.js | 4 +++- test/functional/testCases.js | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index 22217bf9e..81bf1a2a4 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -277,7 +277,9 @@ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, function reduceMetadataAttrToPlainObject(attrs, initObj = {}) { if (attrs !== undefined && Array.isArray(attrs)) { return attrs.reduce((result, item) => { - result['metadata'] = {}; + if (result['metadata'] === undefined) { + result['metadata'] = {}; + } if (item.metadata !== undefined) { result['metadata'][item.name] = {}; for (var meta in item.metadata) { diff --git a/test/functional/testCases.js b/test/functional/testCases.js index 44431d996..3c360197a 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -1843,6 +1843,17 @@ const testCases = [ commands: [], lazy: [], static_attributes: [ + { + name: 'st_attr1', + type: 'Number', + value: 1.5, + metadata: { + coef1: { + value: 0.8, + type: 'Float' + } + } + }, { name: 'st_attr', type: 'Number', @@ -1853,6 +1864,17 @@ const testCases = [ type: 'Float' } } + }, + { + name: 'st_attr2', + type: 'Number', + value: 1.5, + metadata: { + coef2: { + value: 0.8, + type: 'Float' + } + } } ], attributes: [ From 344cb5c66ec74d84ae8270bd70d77b18d61c4bd6 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 6 Aug 2024 11:48:48 +0200 Subject: [PATCH 10/14] update metdata value after evaluate metadata expression --- lib/services/ngsi/entities-NGSI-v2.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index 81bf1a2a4..9feabac61 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -535,6 +535,11 @@ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, } newAttrMeta['value'] = metaValueExpression; currentAttr.metadata[metaKey] = newAttrMeta; + + //RE-Populate de JEXLcontext + if (jexlctxt.metadata && jexlctxt.metadata[currentAttr.name]) { + jexlctxt.metadata[currentAttr.name][currentAttr.metadata[metaKey]] = metaValueExpression; + } } } } From a103f39312bc1bad5f48dbc1a9fdafecba15852e Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 6 Aug 2024 11:50:47 +0200 Subject: [PATCH 11/14] update doc --- doc/api.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/api.md b/doc/api.md index 963336b1c..464f7db82 100644 --- a/doc/api.md +++ b/doc/api.md @@ -596,6 +596,10 @@ Attribute metadata and Static Attribute metadata are available in the **context* `metadata..` or `metadata..` in a similar way of defined for [Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support) +Moreover if attribute metdata has an expression metadata attribute value in jexl context is updated after that +expression is evaluated. Note that there is no order into metadata structure and there is no warranty about which +metadata attribute expression will be evaluated first. + ### Examples of JEXL expressions The following table shows expressions and their expected outcomes taking into account the following measures at From 5da8640beaf25dcb985c3fae496bf02e113c046b Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 6 Aug 2024 14:26:31 +0200 Subject: [PATCH 12/14] allow metadata is still not in context after apply expression over a metadata2 --- lib/services/ngsi/entities-NGSI-v2.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index 9feabac61..ec83729b5 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -537,9 +537,14 @@ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, currentAttr.metadata[metaKey] = newAttrMeta; //RE-Populate de JEXLcontext - if (jexlctxt.metadata && jexlctxt.metadata[currentAttr.name]) { - jexlctxt.metadata[currentAttr.name][currentAttr.metadata[metaKey]] = metaValueExpression; + // It is possible metadata is still not in ctxt + if (!jexlctxt.metadata) { + jexlctxt.metadata = {}; } + if (!jexlctxt.metadata[currentAttr.name]) { + jexlctxt.metadata[currentAttr.name] = {}; + } + jexlctxt.metadata[currentAttr.name][currentAttr.metadata[metaKey]] = metaValueExpression; } } } From ad3e3777e6f8271c60e95563924b09e7b96adfeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferm=C3=ADn=20Gal=C3=A1n=20M=C3=A1rquez?= Date: Wed, 7 Aug 2024 12:52:47 +0200 Subject: [PATCH 13/14] Update doc/api.md --- doc/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index 464f7db82..0126ec857 100644 --- a/doc/api.md +++ b/doc/api.md @@ -596,7 +596,7 @@ Attribute metadata and Static Attribute metadata are available in the **context* `metadata..` or `metadata..` in a similar way of defined for [Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support) -Moreover if attribute metdata has an expression metadata attribute value in jexl context is updated after that +Moreover, if attribute metadata has an expression metadata attribute value in jexl context it is updated after that expression is evaluated. Note that there is no order into metadata structure and there is no warranty about which metadata attribute expression will be evaluated first. From 293d876147b5b222fc3dc9d99390d3bc9e1dda5a Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 7 Aug 2024 12:56:32 +0200 Subject: [PATCH 14/14] Update lib/services/ngsi/entities-NGSI-v2.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fermín Galán Márquez --- lib/services/ngsi/entities-NGSI-v2.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index ec83729b5..4a0d89460 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -273,7 +273,8 @@ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, return initObj; } } - //it returns a flat object from an Metadata Object + //it returns a metadata object using the same structure described + // at https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support function reduceMetadataAttrToPlainObject(attrs, initObj = {}) { if (attrs !== undefined && Array.isArray(attrs)) { return attrs.reduce((result, item) => {