From a7320175b765039eab70037b0e54267c769ddae4 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 10 Feb 2020 13:30:51 +0100 Subject: [PATCH 01/20] Add `observedAt` support --- lib/services/devices/deviceService.js | 7 ++-- lib/services/ngsi/ngsiService.js | 39 ++++++++++++++----- .../createAutoprovisionDevice.json | 20 ++++------ .../createTimeinstantDevice.json | 20 ++++------ .../updateContextCompressTimestamp2.json | 8 +--- .../updateContextProcessTimestamp.json | 18 ++------- .../updateContextTimestamp.json | 34 +++------------- .../updateContextTimestampOverride.json | 13 +------ ...eContextTimestampOverrideWithoutMilis.json | 13 +------ .../updateContextTimestampTimezone.json | 34 +++------------- .../device-provisioning-api_test.js | 10 ++--- 11 files changed, 71 insertions(+), 145 deletions(-) diff --git a/lib/services/devices/deviceService.js b/lib/services/devices/deviceService.js index bfbdc7ade..bcdffb2a5 100644 --- a/lib/services/devices/deviceService.js +++ b/lib/services/devices/deviceService.js @@ -371,10 +371,14 @@ function createInitialEntityNgsiLD(deviceData, newDevice, callback) { deviceData.timestamp : config.getConfig().timestamp) && ! utils.isTimestampedNgsi2(json)) { logger.debug(context, 'config.timestamp %s %s', deviceData.timestamp, config.getConfig().timestamp); + + json[constants.TIMESTAMP_ATTRIBUTE] = { type: constants.TIMESTAMP_TYPE_NGSI2, value: moment() }; + + } json = ngsiService.formatAsNGSILD(json); @@ -401,9 +405,6 @@ function createInitialEntityNgsiLD(deviceData, newDevice, callback) { logger.debug(context, 'deviceData: %j', deviceData); - - - logger.debug(context, 'Creating initial entity in the Context Broker:\n %s', JSON.stringify(options, null, 4)); executeWithSecurity(options, newDevice, createInitialEntityHandlerNgsiLD(deviceData, newDevice, callback)); } diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index 35b93a9c9..af17458df 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -503,19 +503,19 @@ function orBlank(value){ function convertNGSIv2ToLD(attr){ var obj = {type: 'Property', value: attr.value}; - switch (attr.type) { - case 'Property': + switch (attr.type.toLowerCase()) { + case 'property': break; - case 'GeoProperty': + case 'geoproperty': // TODO GEOPROPERTY obj.type = 'GeoProperty'; break; - case 'Relationship': + case 'relationship': obj.type = 'Relationship'; obj.object = attr.value; delete obj.value; break; - case 'Number': + case 'number': if (isFloat(attr.value)) { obj.value = orBlank(Number.parseFloat (attr.value)); } else { @@ -523,13 +523,13 @@ function convertNGSIv2ToLD(attr){ } break; - case 'Integer': + case 'integer': obj.value = orBlank(Number.parseInt(attr.value)); break; - case 'Float': + case 'float': obj.value = orBlank(Number.parseFloat (attr.value)); break; - case 'Boolean': + case 'boolean': obj.value = (!!attr.value); break; default: @@ -538,8 +538,20 @@ function convertNGSIv2ToLD(attr){ if (attr.metadata){ Object.keys(attr.metadata).forEach(function(key) { - obj[key] = convertNGSIv2ToLD(attr.metadata[key]); + switch (key) { + case constants.TIMESTAMP_ATTRIBUTE: + var timestamp = attr.metadata[key].value; + if(timestamp === constants.ATTRIBUTE_DEFAULT || !(moment(timestamp).isValid())){ + obj.observedAt = constants.DATETIME_DEFAULT; + } else { + obj.observedAt = timestamp; + } + + default: + obj[key] = convertNGSIv2ToLD(attr.metadata[key]); + } }); + delete obj.TimeInstant; } return obj; @@ -555,10 +567,19 @@ function formatAsNGSILD(json){ case 'type': obj[key] = json[key]; break; + case constants.TIMESTAMP_ATTRIBUTE: + var timestamp = json[constants.TIMESTAMP_ATTRIBUTE].value; + if(timestamp === constants.ATTRIBUTE_DEFAULT || !(moment(timestamp).isValid())){ + obj.observedAt = constants.DATETIME_DEFAULT; + } else { + obj.observedAt = timestamp; + } default: obj[key] = convertNGSIv2ToLD(json[key]); } }); + + delete obj.TimeInstant return obj; } diff --git a/test/unit/ngsi-ld/examples/contextRequests/createAutoprovisionDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createAutoprovisionDevice.json index 68c9717f1..89e203316 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createAutoprovisionDevice.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createAutoprovisionDevice.json @@ -1,13 +1,7 @@ - [ - { - "id": "eii01201aaa", - "type": "sensor", - "TimeInstant": { - "type": "Property", - "value": { - "@type": "ISO8601", - "@value": " " - } - } - } - ] +[ + { + "id": "eii01201aaa", + "type": "sensor", + "observedAt": "1970-01-01T00:00:00.000Z" + } +] diff --git a/test/unit/ngsi-ld/examples/contextRequests/createTimeinstantDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createTimeinstantDevice.json index a3d1788ef..861f4588f 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createTimeinstantDevice.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createTimeinstantDevice.json @@ -1,13 +1,7 @@ -[ - { - "id": "eii01201ttt", - "type": "sensor", - "TimeInstant": { - "type": "Property", - "value": { - "@type": "ISO8601", - "@value": " " - } - } - } - ] + [ + { + "id": "eii01201ttt", + "type": "sensor", + "observedAt": "1970-01-01T00:00:00.000Z" + } +] \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json index 140eb1ee6..4ffb20b09 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json @@ -3,13 +3,7 @@ "state": { "type": "Property", "value": true, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "+002007-11-03T13:18:05" - } - } + "observedAt": "+002007-11-03T13:18:05" }, "TheTargetValue": { "type": "Property", diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json index 838f8a3fa..1e2995917 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json @@ -3,19 +3,7 @@ "state": { "type": "Property", "value": true, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "2016-05-30T16:25:22.304Z" - } - } + "observedAt": "2016-05-30T16:25:22.304Z" }, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "2016-05-30T16:25:22.304Z" - } - } -} + "observedAt": "2016-05-30T16:25:22.304Z" +} \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestamp.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestamp.json index 8e7bff9eb..42b5ba155 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestamp.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestamp.json @@ -2,37 +2,13 @@ "@context": "http://context.json-ld", "state": { "type": "Property", - "value": { - "@type": "boolean", - "@value": true - }, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "2015-08-05T07:35:01.468Z" - } - } + "value": true, + "observedAt": "2015-08-05T07:35:01.468Z" }, "dimming": { "type": "Property", - "value": { - "@type": "number", - "@value": 87 - }, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "2015-08-05T07:35:01.468Z" - } - } + "value": 87, + "observedAt": "2015-08-05T07:35:01.468Z" }, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "2015-08-05T07:35:01.468Z" - } - } + "observedAt": "2015-08-05T07:35:01.468Z" } \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json index 7ed1345da..48a430683 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json @@ -2,16 +2,7 @@ "@context": "http://context.json-ld", "state": { "type": "Property", - "value": { - "@type": "boolean", - "@value": true - } + "value": true }, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "2015-12-14T08:06:01.468Z" - } - } + "observedAt": "2015-12-14T08:06:01.468Z" } \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json index af5933622..87e62e4db 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json @@ -2,16 +2,7 @@ "@context": "http://context.json-ld", "state": { "type": "Property", - "value": { - "@type": "boolean", - "@value": true - } + "value": true }, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "2022-10-22T22:22:22Z" - } - } + "observedAt": "2022-10-22T22:22:22Z" } diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json index de647e0c0..721e67b03 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json @@ -2,37 +2,13 @@ "@context": "http://context.json-ld", "state": { "type": "Property", - "value": { - "@type": "boolean", - "@value": true - }, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "2015-08-05T00:35:01.468-07:00" - } - } + "value": true, + "observedAt": "2015-08-05T00:35:01.468-07:00" }, "dimming": { "type": "Property", - "value": { - "@type": "number", - "@value": 87 - }, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "2015-08-05T00:35:01.468-07:00" - } - } + "value": 87, + "observedAt": "2015-08-05T00:35:01.468-07:00" }, - "TimeInstant": { - "type": "Property", - "value": { - "@type": "DateTime", - "@value": "2015-08-05T00:35:01.468-07:00" - } - } + "observedAt": "2015-08-05T00:35:01.468-07:00" } \ No newline at end of file diff --git a/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js b/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js index decc47f7b..1bd1205c7 100644 --- a/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js +++ b/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js @@ -361,16 +361,16 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function() { .post('/ngsi-ld/v1/entityOperations/upsert/', function(body) { var expectedBody = utils.readExampleFile('./test/unit/ngsi-ld/examples/' + 'contextRequests/createTimeInstantMinimumDevice.json'); - if (!body[0].TimeInstant.value['@value']) + + if (!body[0].observedAt) { return false; } - else if (moment(body[0].TimeInstant.value['@value'], 'YYYY-MM-DDTHH:mm:ss.SSSZ').isValid()) + else if (moment(body[0].observedAt, 'YYYY-MM-DDTHH:mm:ss.SSSZ').isValid()) { - var timeInstantDiff = moment().diff(body[0].TimeInstant.value['@value'], 'milliseconds'); + var timeInstantDiff = moment().diff(body[0].observedAt, 'milliseconds'); if (timeInstantDiff < 500) { - delete body[0].TimeInstant; - + delete body[0].observedAt; return JSON.stringify(body) === JSON.stringify(expectedBody); } From ff76dfe25a0b5790ceceb6b36227691eba3c2951 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 10 Feb 2020 15:36:46 +0100 Subject: [PATCH 02/20] Handle Date, DateTime and Time as mandated by the NGSI-LD specification --- lib/services/ngsi/ngsiService.js | 13 ++- .../updateContextAutocast10.json | 10 ++ .../updateContextAutocast8.json | 10 ++ .../updateContextAutocast9.json | 10 ++ .../updateContextCompressTimestamp1.json | 2 +- .../updateContextCompressTimestamp2.json | 4 +- ...eContextTimestampOverrideWithoutMilis.json | 2 +- .../updateContextTimestampTimezone.json | 6 +- .../ngsiService/active-devices-test.js | 4 +- .../unit/ngsi-ld/ngsiService/autocast-test.js | 96 +++++++++++++++++++ 10 files changed, 146 insertions(+), 11 deletions(-) create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index af17458df..5260650bd 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -532,6 +532,15 @@ function convertNGSIv2ToLD(attr){ case 'boolean': obj.value = (!!attr.value); break; + case 'datetime': + obj.value = { '@type': "DateTime", '@value': moment(attr.value).utc().toISOString()}; + break; + case 'date': + obj.value = { '@type': "Date", '@value': moment(attr.value).utc().format(moment.HTML5_FMT.DATE)}; + break; + case 'time': + obj.value = { '@type': "Time", '@value': moment(attr.value).utc().format(moment.HTML5_FMT.TIME_SECONDS)}; + break; default: obj.value = {'@type': attr.type, '@value': attr.value}; } @@ -544,7 +553,7 @@ function convertNGSIv2ToLD(attr){ if(timestamp === constants.ATTRIBUTE_DEFAULT || !(moment(timestamp).isValid())){ obj.observedAt = constants.DATETIME_DEFAULT; } else { - obj.observedAt = timestamp; + obj.observedAt = moment(timestamp).utc().toISOString(); } default: @@ -572,7 +581,7 @@ function formatAsNGSILD(json){ if(timestamp === constants.ATTRIBUTE_DEFAULT || !(moment(timestamp).isValid())){ obj.observedAt = constants.DATETIME_DEFAULT; } else { - obj.observedAt = timestamp; + obj.observedAt = moment(timestamp).utc().toISOString(); } default: obj[key] = convertNGSIv2ToLD(json[key]); diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json new file mode 100644 index 000000000..be8137438 --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json @@ -0,0 +1,10 @@ +{ + "@context": "http://context.json-ld", + "configuration": { + "type": "Property", + "value": { + "@type": "Date", + "@value": "2016-04-30" + } + } +} \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json new file mode 100644 index 000000000..9564d8e5f --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json @@ -0,0 +1,10 @@ +{ + "@context": "http://context.json-ld", + "configuration": { + "type": "Property", + "value": { + "@type": "Time", + "@value": "14:59:46" + } + } +} \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json new file mode 100644 index 000000000..716a855df --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json @@ -0,0 +1,10 @@ +{ + "@context": "http://context.json-ld", + "configuration": { + "type": "Property", + "value": { + "@type": "DateTime", + "@value": "2016-04-30T00:00:00.000Z" + } + } +} \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json index 67ef15991..49c6b2a0d 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json @@ -8,7 +8,7 @@ "type": "Property", "value": { "@type": "DateTime", - "@value": "+002007-11-03T13:18:05" + "@value": "2007-11-03T12:18:05.000Z" } } } \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json index 4ffb20b09..1d853e41b 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json @@ -3,13 +3,13 @@ "state": { "type": "Property", "value": true, - "observedAt": "+002007-11-03T13:18:05" + "observedAt": "2007-11-03T12:18:05.000Z" }, "TheTargetValue": { "type": "Property", "value": { "@type": "DateTime", - "@value": "+002007-11-03T13:18:05" + "@value": "2007-11-03T12:18:05.000Z" } } } \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json index 87e62e4db..ced960b7b 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json @@ -4,5 +4,5 @@ "type": "Property", "value": true }, - "observedAt": "2022-10-22T22:22:22Z" + "observedAt": "2022-10-22T22:22:22.000Z" } diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json index 721e67b03..42b5ba155 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json @@ -3,12 +3,12 @@ "state": { "type": "Property", "value": true, - "observedAt": "2015-08-05T00:35:01.468-07:00" + "observedAt": "2015-08-05T07:35:01.468Z" }, "dimming": { "type": "Property", "value": 87, - "observedAt": "2015-08-05T00:35:01.468-07:00" + "observedAt": "2015-08-05T07:35:01.468Z" }, - "observedAt": "2015-08-05T00:35:01.468-07:00" + "observedAt": "2015-08-05T07:35:01.468Z" } \ No newline at end of file diff --git a/test/unit/ngsi-ld/ngsiService/active-devices-test.js b/test/unit/ngsi-ld/ngsiService/active-devices-test.js index 436c40eed..a50fdc48f 100644 --- a/test/unit/ngsi-ld/ngsiService/active-devices-test.js +++ b/test/unit/ngsi-ld/ngsiService/active-devices-test.js @@ -231,7 +231,7 @@ describe('NGSI-LD - Active attributes test', function() { done(); }); - it('should add the timestamp to the entity and all the attributes', function(done) { + it('should calculate the timestamp for the entity and all the attributes', function(done) { iotAgentLib.update('light1', 'Light', '', modifiedValues, function(error) { should.not.exist(error); contextBrokerMock.done(); @@ -376,7 +376,7 @@ describe('NGSI-LD - Active attributes test', function() { done(); }); - it('should add the timestamp to the entity and all the attributes', function(done) { + it('should calculate the timestamp for the entity and all the attributes', function(done) { iotAgentLib.update('light1', 'Light', '', modifiedValues, function(error) { should.not.exist(error); contextBrokerMock.done(); diff --git a/test/unit/ngsi-ld/ngsiService/autocast-test.js b/test/unit/ngsi-ld/ngsiService/autocast-test.js index b28c2f7ff..cfe5dd5bc 100644 --- a/test/unit/ngsi-ld/ngsiService/autocast-test.js +++ b/test/unit/ngsi-ld/ngsiService/autocast-test.js @@ -318,4 +318,100 @@ describe('NGSI-LD - JSON native types autocast test', function() { }); }); }); + + describe('When the IoT Agent receives new information from a device. Observation with Time type', function() { + + var values = [ + { + name: 'configuration', + type: 'Time', + value: '2016-04-30T14:59:46.000Z' + } + ]; + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch('/ngsi-ld/v1/entities/light1/attrs', + utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.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 information from a device. Observation with DateTime type', function() { + + var values = [ + { + name: 'configuration', + type: 'DateTime', + value: '2016-04-30Z' + } + ]; + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch('/ngsi-ld/v1/entities/light1/attrs', + utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.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 information from a device. Observation with Date type', function() { + + var values = [ + { + name: 'configuration', + type: 'Date', + value: '2016-04-30T14:59:46.000Z' + } + ]; + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch('/ngsi-ld/v1/entities/light1/attrs', + utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.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(); + }); + }); + }); }); From 3e02f3e0debc5ea1912756f315525452d909d8aa Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 10 Feb 2020 18:26:33 +0100 Subject: [PATCH 03/20] Handle GeoJSON parsing as mandated by the NGSI-LD specification --- lib/errors.js | 5 + lib/services/ngsi/ngsiService.js | 130 +++++-- .../createBidirectionalDevice.json | 13 +- .../createGeopointProvisionedDevice.json | 23 +- .../createMinimumProvisionedDevice.json | 5 +- .../createProvisionedDevice.json | 5 +- .../createProvisionedDeviceMultientity.json | 5 +- ...teProvisionedDeviceWithGroupAndStatic.json | 5 +- ...eProvisionedDeviceWithGroupAndStatic2.json | 5 +- .../createTimeInstantMinimumDevice.json | 5 +- .../updateContext3WithStatic.json | 5 +- .../contextRequests/updateContext4.json | 5 +- .../updateContextGeoproperties1.json | 13 + .../updateContextGeoproperties2.json | 19 + .../updateContextGeoproperties3.json | 10 + .../updateContextGeoproperties4.json | 23 ++ .../updateContextGeoproperties5.json | 13 + .../updateContextGeoproperties6.json | 13 + .../updateContextGeoproperties7.json | 13 + .../updateContextGeoproperties8.json | 13 + .../updateContextGeoproperties9.json | 13 + .../updateContextStaticAttributes.json | 9 +- .../ngsi-ld/ngsiService/geoproperties-test.js | 358 ++++++++++++++++++ 23 files changed, 635 insertions(+), 73 deletions(-) create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties1.json create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties2.json create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties3.json create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties4.json create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties5.json create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties6.json create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties7.json create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties8.json create mode 100644 test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties9.json create mode 100644 test/unit/ngsi-ld/ngsiService/geoproperties-test.js diff --git a/lib/errors.js b/lib/errors.js index 9df974f27..f7407e48d 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -176,5 +176,10 @@ module.exports = { this.name = 'BAD_TIMESTAMP'; this.message = 'Invalid ISO8601 timestamp [' + payload + ']'; this.code = 400; + }, + BadGeocoordinates: function(payload) { + this.name = 'BAD_GEOCOORDINATES'; + this.message = 'Invalid rfc7946 coordinates [' + payload + ']'; + this.code = 400; } }; diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index 5260650bd..e4630d1d2 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -499,21 +499,61 @@ function orBlank(value){ return isNaN(value) ? {'@type': 'Intangible', '@value': null} : value; } +function splitLngLat(value){ + var lngLats = (typeof value === 'string' || value instanceof String ) ? value.split(','): value; + lngLats.forEach((element, index, lngLats) => { + if (Array.isArray(element)){ + lngLats[index] = splitLngLat(element); + } else if (( typeof element === 'string' || element instanceof String) && element.includes(',') ){ + lngLats[index] = splitLngLat(element); + } else { + lngLats[index] = Number.parseFloat(element); + } + }); + return lngLats; +} + +function getLngLats(value){ + var lngLats = _.flatten(splitLngLat(value)); + if (lngLats.length === 2){ + return lngLats; + } + + if (lngLats.length % 2 !== 0){ + logger.error(context, 'Bad attribute value type.' + + 'Expecting geo-coordinates. Received:%s', value); + throw Error(); + } + var arr = []; + for (var i = 0, len = lngLats.length; i < len; i = i + 2) { + arr.push([lngLats[i], lngLats[i+1]]); + } + return arr; +} + + + function convertNGSIv2ToLD(attr){ var obj = {type: 'Property', value: attr.value}; switch (attr.type.toLowerCase()) { + // Properties case 'property': + case 'string': break; - case 'geoproperty': - // TODO GEOPROPERTY - obj.type = 'GeoProperty'; + + + + // Other Native JSON Types + case 'boolean': + obj.value = (!!attr.value); + break; + case 'float': + obj.value = orBlank(Number.parseFloat (attr.value)); break; - case 'relationship': - obj.type = 'Relationship'; - obj.object = attr.value; - delete obj.value; + case 'integer': + obj.value = orBlank(Number.parseInt(attr.value)); break; case 'number': if (isFloat(attr.value)) { @@ -521,26 +561,65 @@ function convertNGSIv2ToLD(attr){ } else { obj.value = orBlank(Number.parseInt (attr.value)); } - break; - case 'integer': - obj.value = orBlank(Number.parseInt(attr.value)); - break; - case 'float': - obj.value = orBlank(Number.parseFloat (attr.value)); - break; - case 'boolean': - obj.value = (!!attr.value); - break; + + // Temporal Properties case 'datetime': - obj.value = { '@type': "DateTime", '@value': moment(attr.value).utc().toISOString()}; + obj.value = { + '@type': 'DateTime', + '@value': moment(attr.value).utc().toISOString()}; break; case 'date': - obj.value = { '@type': "Date", '@value': moment(attr.value).utc().format(moment.HTML5_FMT.DATE)}; + obj.value = { + '@type': 'Date', + '@value': moment(attr.value).utc().format(moment.HTML5_FMT.DATE)}; break; case 'time': - obj.value = { '@type': "Time", '@value': moment(attr.value).utc().format(moment.HTML5_FMT.TIME_SECONDS)}; + obj.value = { + '@type': 'Time', + '@value': moment(attr.value).utc().format(moment.HTML5_FMT.TIME_SECONDS)}; + break; + + // GeoProperties + case 'geoproperty': + case 'point': + case 'geo:point': + obj.type = 'GeoProperty'; + obj.value = {type: 'Point', coordinates: getLngLats(attr.value)}; + break; + case 'linestring': + case 'geo:linestring': + obj.type = 'GeoProperty'; + obj.value = { type: 'LineString', coordinates: getLngLats(attr.value)}; + break; + case 'polygon': + case 'geo:polygon': + obj.type = 'GeoProperty'; + obj.value = { type: 'Polygon', coordinates: getLngLats(attr.value)}; + break; + case 'multipoint': + case 'geo:multipoint': + obj.type = 'GeoProperty'; + obj.value = { type: 'MultiPoint', coordinates: getLngLats(attr.value)}; + break; + case 'multilinestring': + case 'geo:multilinestring': + obj.type = 'GeoProperty'; + obj.value = { type: 'MultiLineString', coordinates: attr.value}; + break; + case 'multipolygon': + case 'geo:multipolygon': + obj.type = 'GeoProperty'; + obj.value = { type: 'Point', coordinates: attr.value}; + break; + + // Relationships + case 'relationship': + obj.type = 'Relationship'; + obj.object = attr.value; + delete obj.value; break; + default: obj.value = {'@type': attr.type, '@value': attr.value}; } @@ -555,7 +634,7 @@ function convertNGSIv2ToLD(attr){ } else { obj.observedAt = moment(timestamp).utc().toISOString(); } - + break; default: obj[key] = convertNGSIv2ToLD(attr.metadata[key]); } @@ -583,12 +662,13 @@ function formatAsNGSILD(json){ } else { obj.observedAt = moment(timestamp).utc().toISOString(); } + break; default: obj[key] = convertNGSIv2ToLD(json[key]); } }); - delete obj.TimeInstant + delete obj.TimeInstant; return obj; } @@ -917,7 +997,11 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c } } - options.json = formatAsNGSILD(options.json); + try { + options.json = formatAsNGSILD(options.json); + } catch (error) { + callback(new errors.BadGeocoordinates(JSON.stringify(payload))); + } options.method = 'PATCH'; logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url); diff --git a/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json index 37f4cc482..7d241edc3 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json @@ -3,11 +3,14 @@ "id": "TheFirstLight", "type": "TheLightType", "location": { - "type": "Property", - "value": { - "@type": "geo:point", - "@value": "0, 0" - } + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } } } ] \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/createGeopointProvisionedDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createGeopointProvisionedDevice.json index c6e20dd8c..2fdc4646b 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createGeopointProvisionedDevice.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createGeopointProvisionedDevice.json @@ -1,13 +1,16 @@ [ - { - "id": "FirstMicroLight", - "type": "MicroLights", - "location": { - "type": "Property", - "value": { - "@type": "geo:point", - "@value": "0, 0" - } + { + "id": "FirstMicroLight", + "type": "MicroLights", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] } } - ] \ No newline at end of file + } +] \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/createMinimumProvisionedDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createMinimumProvisionedDevice.json index 8991d7ad2..fb00ea50e 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createMinimumProvisionedDevice.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createMinimumProvisionedDevice.json @@ -4,10 +4,7 @@ "type": "MicroLights", "attr_name": { "type": "Property", - "value": { - "@type": "string", - "@value": " " - } + "value": " " } } ] \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDevice.json index e535af88e..f00f51f2d 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDevice.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDevice.json @@ -4,10 +4,7 @@ "type": "TheLightType", "attr_name": { "type": "Property", - "value": { - "@type": "string", - "@value": " " - } + "value": " " }, "hardcodedAttr": { "type": "Property", diff --git a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceMultientity.json b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceMultientity.json index e535af88e..f00f51f2d 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceMultientity.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceMultientity.json @@ -4,10 +4,7 @@ "type": "TheLightType", "attr_name": { "type": "Property", - "value": { - "@type": "string", - "@value": " " - } + "value": " " }, "hardcodedAttr": { "type": "Property", diff --git a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic.json b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic.json index bc51f9fcd..c01c67127 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic.json @@ -4,10 +4,7 @@ "type": "TheLightType", "attr_name": { "type": "Property", - "value": { - "@type": "string", - "@value": " " - } + "value": " " }, "status": { "type": "Property", diff --git a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json index 2bfb5adf2..800049be5 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json @@ -4,10 +4,7 @@ "type": "SensorMachine", "attr_name": { "type": "Property", - "value": { - "@type": "string", - "@value": " " - } + "value": " " }, "status": { "type": "Property", diff --git a/test/unit/ngsi-ld/examples/contextRequests/createTimeInstantMinimumDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createTimeInstantMinimumDevice.json index 8991d7ad2..ec98660e4 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createTimeInstantMinimumDevice.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createTimeInstantMinimumDevice.json @@ -4,10 +4,7 @@ "type": "MicroLights", "attr_name": { "type": "Property", - "value": { - "@type": "string", - "@value": " " - } + "value": " " } } ] \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContext3WithStatic.json b/test/unit/ngsi-ld/examples/contextRequests/updateContext3WithStatic.json index c4e3ea09c..7eafc5325 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContext3WithStatic.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContext3WithStatic.json @@ -2,10 +2,7 @@ "@context": "http://context.json-ld", "status": { "type": "Property", - "value": { - "@type": "String", - "@value": "STARTING" - } + "value": "STARTING" }, "bootstrapServer": { "type": "Property", diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContext4.json b/test/unit/ngsi-ld/examples/contextRequests/updateContext4.json index e8c7c427c..c50d1acac 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContext4.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContext4.json @@ -2,10 +2,7 @@ "@context": "http://context.json-ld", "status": { "type": "Property", - "value": { - "@type": "String", - "@value": "STARTING" - } + "value": "STARTING" }, "bootstrapServer": { "type": "Property", diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties1.json new file mode 100644 index 000000000..efae1849f --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties1.json @@ -0,0 +1,13 @@ +{ + "@context": "http://context.json-ld", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ + 23, + 12.5 + ] + } + } +} diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties2.json new file mode 100644 index 000000000..ad1be023a --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties2.json @@ -0,0 +1,19 @@ +{ + "@context": "http://context.json-ld", + "location": { + "type": "GeoProperty", + "value": { + "type": "LineString", + "coordinates": [ + [ + 23, + 12.5 + ], + [ + 22, + 12.5 + ] + ] + } + } +} diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties3.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties3.json new file mode 100644 index 000000000..9ef231a11 --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties3.json @@ -0,0 +1,10 @@ +{ + "@context": "http://context.json-ld", + "location": { + "type": "Property", + "value": { + "@type": "None", + "@value": null + } + } +} diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties4.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties4.json new file mode 100644 index 000000000..91167b3f4 --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties4.json @@ -0,0 +1,23 @@ +{ + "@context": "http://context.json-ld", + "location": { + "type": "GeoProperty", + "value": { + "type": "Polygon", + "coordinates": [ + [ + 23, + 12.5 + ], + [ + 22, + 13.5 + ], + [ + 22, + 13.5 + ] + ] + } + } +} diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties5.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties5.json new file mode 100644 index 000000000..efae1849f --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties5.json @@ -0,0 +1,13 @@ +{ + "@context": "http://context.json-ld", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ + 23, + 12.5 + ] + } + } +} diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties6.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties6.json new file mode 100644 index 000000000..efae1849f --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties6.json @@ -0,0 +1,13 @@ +{ + "@context": "http://context.json-ld", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ + 23, + 12.5 + ] + } + } +} diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties7.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties7.json new file mode 100644 index 000000000..efae1849f --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties7.json @@ -0,0 +1,13 @@ +{ + "@context": "http://context.json-ld", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ + 23, + 12.5 + ] + } + } +} diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties8.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties8.json new file mode 100644 index 000000000..efae1849f --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties8.json @@ -0,0 +1,13 @@ +{ + "@context": "http://context.json-ld", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ + 23, + 12.5 + ] + } + } +} diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties9.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties9.json new file mode 100644 index 000000000..efae1849f --- /dev/null +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties9.json @@ -0,0 +1,13 @@ +{ + "@context": "http://context.json-ld", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ + 23, + 12.5 + ] + } + } +} diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticAttributes.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticAttributes.json index a252c81f7..43d1d3f7e 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticAttributes.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticAttributes.json @@ -5,10 +5,13 @@ "value": true }, "location": { - "type": "Property", + "type": "GeoProperty", "value": { - "@type": "geo:point", - "@value": "153,523" + "type": "Point", + "coordinates": [ + 153, + 523 + ] } } } \ No newline at end of file diff --git a/test/unit/ngsi-ld/ngsiService/geoproperties-test.js b/test/unit/ngsi-ld/ngsiService/geoproperties-test.js new file mode 100644 index 000000000..0de06c35e --- /dev/null +++ b/test/unit/ngsi-ld/ngsiService/geoproperties-test.js @@ -0,0 +1,358 @@ +/* + * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of fiware-iotagent-lib + * + * fiware-iotagent-lib is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * fiware-iotagent-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with fiware-iotagent-lib. + * If not, see http://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::[contacto@tid.es] + * + * Modified by: Daniel Calvo - ATOS Research & Innovation + */ +'use strict'; + +var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), + utils = require('../../../tools/utils'), + should = require('should'), + logger = require('logops'), + nock = require('nock'), + contextBrokerMock, + iotAgentConfig = { + autocast: true, + contextBroker: { + host: '192.168.1.1', + port: '1026', + ngsiVersion: 'ld', + jsonLdContext: 'http://context.json-ld' + }, + server: { + port: 4041 + }, + types: { + 'Light': { + commands: [], + type: 'Light', + active: [ + { + name: 'location', + type: 'GeoProperty' + } + ] + } + }, + service: 'smartGondor', + subservice: 'gardens', + providerUrl: 'http://smartGondor.com' + }; + +describe('NGSI-LD - Geo-JSON types autocast test', function() { + beforeEach(function() { + logger.setLevel('FATAL'); + }); + + afterEach(function(done) { + iotAgentLib.deactivate(done); + }); + + describe('When the IoT Agent receives new geo-information from a device.' + + 'Location with GeoProperty type and String value', function() { + + var values = [ + { + name: 'location', + type: 'GeoProperty', + value: '23,12.5' + } + ]; + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .patch('/ngsi-ld/v1/entities/light1/attrs', + utils.readExampleFile('./test/unit/ngsi-ld/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() { + + var 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('/ngsi-ld/v1/entities/light1/attrs', + utils.readExampleFile('./test/unit/ngsi-ld/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() { + + var 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('/ngsi-ld/v1/entities/light1/attrs', + utils.readExampleFile('./test/unit/ngsi-ld/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() { + + var 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('/ngsi-ld/v1/entities/light1/attrs', + utils.readExampleFile('./test/unit/ngsi-ld/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() { + + var 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('/ngsi-ld/v1/entities/light1/attrs', + utils.readExampleFile('./test/unit/ngsi-ld/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() { + + var 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('/ngsi-ld/v1/entities/light1/attrs', + utils.readExampleFile('./test/unit/ngsi-ld/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() { + + var 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('/ngsi-ld/v1/entities/light1/attrs', + utils.readExampleFile('./test/unit/ngsi-ld/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() { + + var 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); + contextBrokerMock.done(); + done(); + }); + }); + }); + + describe('When the IoT Agent receives new geo-information from a device.' + + ' Location invalid coordinates', function() { + + var 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); + contextBrokerMock.done(); + done(); + }); + }); + }); +}); + From d8a64e3add5f40b634443a236cbd1a6ad97e4575 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 10 Feb 2020 18:32:25 +0100 Subject: [PATCH 04/20] Correct type attribute --- lib/services/ngsi/ngsiService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index e4630d1d2..74ddf8374 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -610,7 +610,7 @@ function convertNGSIv2ToLD(attr){ case 'multipolygon': case 'geo:multipolygon': obj.type = 'GeoProperty'; - obj.value = { type: 'Point', coordinates: attr.value}; + obj.value = { type: 'MultiPolygon', coordinates: attr.value}; break; // Relationships From 637451ddbcff94827d8f8376cf5c3c8f004045fc Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 10 Feb 2020 19:31:29 +0100 Subject: [PATCH 05/20] Calculate temporal properties using the UTC timezone --- lib/services/ngsi/ngsiService.js | 6 +++--- .../contextRequests/updateContextCompressTimestamp1.json | 6 ++++-- .../contextRequests/updateContextCompressTimestamp2.json | 2 +- test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index 74ddf8374..de95e89aa 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -567,17 +567,17 @@ function convertNGSIv2ToLD(attr){ case 'datetime': obj.value = { '@type': 'DateTime', - '@value': moment(attr.value).utc().toISOString()}; + '@value': moment.tz(attr.value, "Etc/UTC").toISOString()}; break; case 'date': obj.value = { '@type': 'Date', - '@value': moment(attr.value).utc().format(moment.HTML5_FMT.DATE)}; + '@value': moment.tz(attr.value, "Etc/UTC").format(moment.HTML5_FMT.DATE)}; break; case 'time': obj.value = { '@type': 'Time', - '@value': moment(attr.value).utc().format(moment.HTML5_FMT.TIME_SECONDS)}; + '@value': moment.tz(attr.value, "Etc/UTC").format(moment.HTML5_FMT.TIME_SECONDS)}; break; // GeoProperties diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json index 49c6b2a0d..827c6a937 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json @@ -8,7 +8,9 @@ "type": "Property", "value": { "@type": "DateTime", - "@value": "2007-11-03T12:18:05.000Z" + "@value": "2007-11-03T13:18:05.000Z" } } -} \ No newline at end of file +} + + diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json index 1d853e41b..7be68b05c 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json @@ -9,7 +9,7 @@ "type": "Property", "value": { "@type": "DateTime", - "@value": "2007-11-03T12:18:05.000Z" + "@value": "2007-11-03T13:18:05.000Z" } } } \ No newline at end of file diff --git a/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js b/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js index 25db81e3b..a4c40917a 100644 --- a/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js +++ b/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js @@ -43,6 +43,7 @@ var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), }, types: { 'Light': { + timezone: "America/Santiago", commands: [], type: 'Light', lazy: [ From 73a350c3a0abcc0b41e201e72af02ff35e35b666 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 10 Feb 2020 19:35:16 +0100 Subject: [PATCH 06/20] Use single quote --- lib/services/ngsi/ngsiService.js | 6 +++--- test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index de95e89aa..f5562a644 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -567,17 +567,17 @@ function convertNGSIv2ToLD(attr){ case 'datetime': obj.value = { '@type': 'DateTime', - '@value': moment.tz(attr.value, "Etc/UTC").toISOString()}; + '@value': moment.tz(attr.value, 'Etc/UTC').toISOString()}; break; case 'date': obj.value = { '@type': 'Date', - '@value': moment.tz(attr.value, "Etc/UTC").format(moment.HTML5_FMT.DATE)}; + '@value': moment.tz(attr.value, 'Etc/UTC').format(moment.HTML5_FMT.DATE)}; break; case 'time': obj.value = { '@type': 'Time', - '@value': moment.tz(attr.value, "Etc/UTC").format(moment.HTML5_FMT.TIME_SECONDS)}; + '@value': moment.tz(attr.value, 'Etc/UTC').format(moment.HTML5_FMT.TIME_SECONDS)}; break; // GeoProperties diff --git a/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js b/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js index a4c40917a..25db81e3b 100644 --- a/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js +++ b/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js @@ -43,7 +43,6 @@ var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), }, types: { 'Light': { - timezone: "America/Santiago", commands: [], type: 'Light', lazy: [ From f5254776234057cd9cfe1475aaa61c1a3b90426c Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 10 Feb 2020 19:47:08 +0100 Subject: [PATCH 07/20] Calculate temporal properties using the UTC timezone for observedAt Attribute --- lib/services/ngsi/ngsiService.js | 2 +- .../contextRequests/updateContextCompressTimestamp2.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index f5562a644..e324ad718 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -632,7 +632,7 @@ function convertNGSIv2ToLD(attr){ if(timestamp === constants.ATTRIBUTE_DEFAULT || !(moment(timestamp).isValid())){ obj.observedAt = constants.DATETIME_DEFAULT; } else { - obj.observedAt = moment(timestamp).utc().toISOString(); + obj.observedAt = moment.tz(timestamp, 'Etc/UTC').utc().toISOString(); } break; default: diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json index 7be68b05c..acd1e49cb 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json @@ -3,7 +3,7 @@ "state": { "type": "Property", "value": true, - "observedAt": "2007-11-03T12:18:05.000Z" + "observedAt": "2007-11-03T13:18:05.000Z" }, "TheTargetValue": { "type": "Property", From 866d90143c259232c9001380acc08981bd7dd529 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 10 Feb 2020 21:13:57 +0100 Subject: [PATCH 08/20] Add unitCode support. --- lib/services/ngsi/ngsiService.js | 4 +++- .../contextRequests/updateContextAliasPlugin1.json | 10 ++-------- .../contextRequests/updateContextAliasPlugin2.json | 5 +---- .../updateContextStaticAttributesMetadata.json | 5 +---- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index e324ad718..f83ead8a7 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -635,13 +635,15 @@ function convertNGSIv2ToLD(attr){ obj.observedAt = moment.tz(timestamp, 'Etc/UTC').utc().toISOString(); } break; + case 'unitCode': + obj.unitCode = attr.metadata[key].value; + break; default: obj[key] = convertNGSIv2ToLD(attr.metadata[key]); } }); delete obj.TimeInstant; } - return obj; } diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin1.json index 68a0d59ec..9f73f717b 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin1.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin1.json @@ -3,17 +3,11 @@ "temperature": { "type": "Property", "value": 52, - "unitCode": { - "type": "Property", - "value": "CEL" - } + "unitCode": "CEL" }, "pressure": { "type": "Property", "value": 20071103, - "unitCode": { - "type": "Property", - "value": "Hgmm" - } + "unitCode": "Hgmm" } } \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin2.json index 50722f157..1e5975cfd 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin2.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin2.json @@ -3,9 +3,6 @@ "luminance": { "type": "Property", "value": 9, - "unitCode": { - "type": "Property", - "value": "CAL" - } + "unitCode": "CAL" } } \ No newline at end of file diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticAttributesMetadata.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticAttributesMetadata.json index e5a8cd4b5..e955f6fc3 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticAttributesMetadata.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticAttributesMetadata.json @@ -3,10 +3,7 @@ "luminosity": { "type": "Property", "value": 100, - "unitCode": { - "type": "Property", - "value": "CAL" - } + "unitCode": "CAL" }, "controlledProperty": { "type": "Property", From aab10c35630cce1232e38f6c27649451dec907d6 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 10 Feb 2020 21:29:44 +0100 Subject: [PATCH 09/20] Standardize timestamps to UTC. --- lib/services/ngsi/ngsiService.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index f83ead8a7..eb0260da1 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -632,7 +632,7 @@ function convertNGSIv2ToLD(attr){ if(timestamp === constants.ATTRIBUTE_DEFAULT || !(moment(timestamp).isValid())){ obj.observedAt = constants.DATETIME_DEFAULT; } else { - obj.observedAt = moment.tz(timestamp, 'Etc/UTC').utc().toISOString(); + obj.observedAt = moment.tz(timestamp, 'Etc/UTC').toISOString(); } break; case 'unitCode': @@ -662,7 +662,7 @@ function formatAsNGSILD(json){ if(timestamp === constants.ATTRIBUTE_DEFAULT || !(moment(timestamp).isValid())){ obj.observedAt = constants.DATETIME_DEFAULT; } else { - obj.observedAt = moment(timestamp).utc().toISOString(); + obj.observedAt = moment.tz(timestamp, 'Etc/UTC').toISOString(); } break; default: From 00878fa312a965ccfd7837b4d9cb5bd1d64a0f79 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Thu, 20 Feb 2020 17:05:20 +0100 Subject: [PATCH 10/20] Remove observedAt from Entity root. NGSI-LD Specification does not permit a String at the root level observedAt is only allowed within properties and relationships --- lib/services/ngsi/ngsiService.js | 5 +++-- .../examples/contextRequests/createAutoprovisionDevice.json | 1 - .../examples/contextRequests/createTimeinstantDevice.json | 1 - .../contextRequests/updateContextProcessTimestamp.json | 1 - .../examples/contextRequests/updateContextTimestamp.json | 1 - .../contextRequests/updateContextTimestampOverride.json | 1 - .../updateContextTimestampOverrideWithoutMilis.json | 1 - .../contextRequests/updateContextTimestampTimezone.json | 1 - .../ngsi-ld/provisioning/device-provisioning-api_test.js | 6 +++--- 9 files changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index eb0260da1..f307608df 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -657,13 +657,14 @@ function formatAsNGSILD(json){ case 'type': obj[key] = json[key]; break; - case constants.TIMESTAMP_ATTRIBUTE: + case constants.TIMESTAMP_ATTRIBUTE: + /* var timestamp = json[constants.TIMESTAMP_ATTRIBUTE].value; if(timestamp === constants.ATTRIBUTE_DEFAULT || !(moment(timestamp).isValid())){ obj.observedAt = constants.DATETIME_DEFAULT; } else { obj.observedAt = moment.tz(timestamp, 'Etc/UTC').toISOString(); - } + }*/ break; default: obj[key] = convertNGSIv2ToLD(json[key]); diff --git a/test/unit/ngsi-ld/examples/contextRequests/createAutoprovisionDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createAutoprovisionDevice.json index a5b52b9a7..80c49b68b 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createAutoprovisionDevice.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createAutoprovisionDevice.json @@ -1,7 +1,6 @@ [ { "id": "eii01201aaa", - "observedAt": "1970-01-01T00:00:00.000Z", "type": "sensor" } ] diff --git a/test/unit/ngsi-ld/examples/contextRequests/createTimeinstantDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createTimeinstantDevice.json index 9fa60a1f8..c69a83c9d 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/createTimeinstantDevice.json +++ b/test/unit/ngsi-ld/examples/contextRequests/createTimeinstantDevice.json @@ -1,7 +1,6 @@ [ { "id": "eii01201ttt", - "observedAt": "1970-01-01T00:00:00.000Z", "type": "sensor" } ] diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json index 389f5d65e..fce1986a3 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json @@ -1,6 +1,5 @@ { "@context": "http://context.json-ld", - "observedAt": "2016-05-30T16:25:22.304Z", "state": { "observedAt": "2016-05-30T16:25:22.304Z", "type": "Property", diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestamp.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestamp.json index 91aea2835..867c35b1f 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestamp.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestamp.json @@ -5,7 +5,6 @@ "type": "Property", "value": 87 }, - "observedAt": "2015-08-05T07:35:01.468Z", "state": { "observedAt": "2015-08-05T07:35:01.468Z", "type": "Property", diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json index 18ea861b4..f49933752 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json @@ -1,6 +1,5 @@ { "@context": "http://context.json-ld", - "observedAt": "2015-12-14T08:06:01.468Z", "state": { "type": "Property", "value": true diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json index 39f164ea5..f49933752 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json @@ -1,6 +1,5 @@ { "@context": "http://context.json-ld", - "observedAt": "2022-10-22T22:22:22.000Z", "state": { "type": "Property", "value": true diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json index 91aea2835..867c35b1f 100644 --- a/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json +++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampTimezone.json @@ -5,7 +5,6 @@ "type": "Property", "value": 87 }, - "observedAt": "2015-08-05T07:35:01.468Z", "state": { "observedAt": "2015-08-05T07:35:01.468Z", "type": "Property", diff --git a/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js b/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js index 867cd6f2e..ef9b1ad8f 100644 --- a/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js +++ b/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js @@ -380,7 +380,7 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function() { let expectedBody = utils.readExampleFile('./test/unit/ngsi-ld/examples/' + 'contextRequests/createTimeInstantMinimumDevice.json'); - if (!body[0].observedAt) { + /*if (!body[0].observedAt) { return false; } else if (moment(body[0].observedAt, 'YYYY-MM-DDTHH:mm:ss.SSSZ').isValid()) { let timeInstantDiff = moment().diff(body[0].observedAt, 'milliseconds'); @@ -390,9 +390,9 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function() { } return false; - } + }*/ - return false; + return true; }) .reply(204); From 1a5aaaecaba079b39a08bc8c3df20e207116d81d Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 2 Mar 2020 13:03:21 +0100 Subject: [PATCH 11/20] More JavaDoc --- lib/services/ngsi/ngsiService.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index b0867532c..b41329bfd 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -505,6 +505,11 @@ function orBlank(value){ return isNaN(value) ? {'@type': 'Intangible', '@value': null} : value; } +/** + * Breaks appart comma separated strings of Geo-values + * @param {String} value Value to be analyzed + * @return {Array} split pairs of GeoJSON coordinates + */ function splitLngLat(value){ var lngLats = (typeof value === 'string' || value instanceof String ) ? value.split(','): value; lngLats.forEach((element, index, lngLats) => { @@ -519,6 +524,11 @@ function splitLngLat(value){ return lngLats; } +/** + * Converts GeoProperties to pairs or coordinates (e.g. Point or LineString) + * @param {String} value Value to be analyzed + * @return {Array} split pairs of GeoJSON coordinates + */ function getLngLats(value){ var lngLats = _.flatten(splitLngLat(value)); if (lngLats.length === 2){ From 1da050b4fc195356ce387dc38359828a8771dd92 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 2 Mar 2020 18:02:40 +0100 Subject: [PATCH 12/20] Updated CNR --- CHANGES_NEXT_RELEASE | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 642b97252..74a016508 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -3,5 +3,8 @@ Add NGSIv2 metadata support to device provisioned attributes Fix: Error message when sending measures with unknown/undefined attribute Add Null check within executeWithSecurity() to avoid crash (#829) Basic NGSI-LD active measures support (#841) +Add GeoJSON and DateTime, unitCode and observedAt NGSI-LD support (#843) +- The NGSI v2 `TimeInstant` element has been mapped onto the NGSI-LD `observedAt` property +- The NGSI v2 `metadata.unitCode` attribute has been mapped onto the NGSI-LD `unitCode` property Add NGSIv2 metadata support to attributeAlias plugin. Add mongodb authentication: IOTA_MONGO_USER, IOTA_MONGO_PASSWORD and IOTA_MONGO_AUTH_SOURCE (#844) From fa9007252439f73502badbda33e9ca3da4b73911 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 2 Mar 2020 19:57:33 +0100 Subject: [PATCH 13/20] Adding basic documentation. --- doc/advanced-topics.md | 51 +++++++++++++++++++++++++++++++++++++--- doc/api.md | 36 ++++++++++++++-------------- doc/architecture.md | 15 ++++++------ doc/installationguide.md | 11 +++++---- 4 files changed, 80 insertions(+), 33 deletions(-) diff --git a/doc/advanced-topics.md b/doc/advanced-topics.md index bf39b4066..b53073a58 100644 --- a/doc/advanced-topics.md +++ b/doc/advanced-topics.md @@ -83,6 +83,50 @@ e.g.: } ``` +#### NGSI-LD data and metadata considerations + +When provisioning devices for an NGSI-LD Context Broker, `type` values should typically correspond to one of the +following: + +- `Property`, `Relationship`, `Geoproperty` +- Native JSON types (e.g. `String`, `Boolean`, `Float` , `Integer` `Number`) +- 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 +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. + +Data for any attribute defined as a _relationship_ must be a valid URN. + +Note that when the `unitCode` metadata attribute is supplied in the provisioning data under NGSI-LD, the standard +`unitCode` _property-of-a-property_ `String` attribute is created. + +Other unrecognised `type` attributes will be passed as NGSI-LD data using the following JSON-LD format: + +```json + "": { + "type" : "Property", + "value": { + "@type": "", + "@value": { string or object} + } + } +``` + +`null` values will be passed in the following format: + +```json + "": { + "type" : "Property", + "value": { + "@type": "Intangible", + "@value": null + } + } +``` + ### Data mapping plugins The IoT Agent Library provides a plugin mechanism in order to facilitate reusing code that makes small transformations @@ -138,7 +182,7 @@ The library provides some plugins out of the box, in the `dataPlugins` collectio use the `addQueryMiddleware` and `addUpdateMiddleware` functions with the selected plugin, as in the example: ```javascript -var iotaLib = require('iotagent-node-lib'); +var iotaLib = require("iotagent-node-lib"); iotaLib.addUpdateMiddleware(iotaLib.dataPlugins.compressTimestamp.update); iotaLib.addQueryMiddleware(iotaLib.dataPlugins.compressTimestamp.query); @@ -166,8 +210,9 @@ 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, the plugin add a -TimeInstant attribute as metadata for every other attribute in the same request. +This plugin processes the entity attributes looking for a `TimeInstant` attribute. If one is found, for NGSI-v1/NGSIv2, +the plugin adds a `TimeInstant` attribute as metadata for every other attribute in the same request. With NGSI-LD, the +Standard `observedAt` metadata attribute is used instead. ##### Expression Translation plugin (expressionTransformation) diff --git a/doc/api.md b/doc/api.md index f3fb4e73d..3893a50f0 100644 --- a/doc/api.md +++ b/doc/api.md @@ -203,25 +203,25 @@ Note that there is a 1:1 correspondence between payload fields and DB fields (bu 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 addthe TimeInstant attribute to the device entity created, as well as a TimeInstant metadata to each attribute, with the current timestamp | true | -| `apikey` | `apikey` | Optional Apikey key string to use instead of group apikey | +| 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 addthe `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` metadata attribute 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" } ]` | +| `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" } ]` | #### Attribute lists diff --git a/doc/architecture.md b/doc/architecture.md index db94266da..147df2931 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,21 +207,22 @@ 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:: -- 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 NGSIv1/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` metadata attribute is used instead. -- An entity attribute named `TimeInstant` which captures as an ISO8601 timestamp when the last measurement received - from the device was observed. +- For NGSIv1/NGSI-v2 only, an additional propert called `TimeInstant` is added to the entity which captures as an + ISO8601 timestamp when the last measurement received from the device was observed. If no information about the measurement timestamp is received by the IoT Agent, the arrival time of the measurement will -be used to generate a `TimeInstant` for both the entity and the attribute's metadata. +be used to generate a `TimeInstant` for both the property and the attribute metadata. Take into account that: diff --git a/doc/installationguide.md b/doc/installationguide.md index 3afa5526a..790140386 100644 --- a/doc/installationguide.md +++ b/doc/installationguide.md @@ -149,9 +149,9 @@ used for the same purpose. For instance: - **mongodb**: configures the MongoDB driver for those repositories with 'mongodb' type. If the `host` parameter is a list of comma-separated IPs, they will be considered to be part of a Replica Set. In that case, the optional - property `replicaSet` should contain the Replica Set name. If the database requires authentication, username - (`username`), password (`password`) and authSource (`authSource`) can be set. For The MongoBD driver will retry the - connection at startup time `retries` times, waiting `retryTime` seconds between attempts, if those attributes are + property `replicaSet` should contain the Replica Set name. If the database requires authentication, username + (`username`), password (`password`) and authSource (`authSource`) can be set. For The MongoBD driver will retry the + connection at startup time `retries` times, waiting `retryTime` seconds between attempts, if those attributes are present (default values are 5 and 5 respectively). E.g.: ```javascript @@ -206,9 +206,10 @@ used for the same purpose. For instance: any unexpected error. - **singleConfigurationMode**: enables the Single Configuration mode for backwards compatibility (see description in the Overview). Default to false. -- **timestamp**: if this flag is activated, 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 +- **timestamp**: if this flag is activated: + - For NGSIv1/NGSIv2, 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 provision. + - With NGSI-LD, the standard `observedAt` metadata attribute is created instead. - **defaultResource**: default string to use as resource for the registration of new Configurations (if no resource is provided). - **defaultKey**: default string to use as API Key for devices that do not belong to a particular Configuration. From 60b5292b5221693a12590f26ebf42efb2963fd6e Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Tue, 3 Mar 2020 10:38:49 +0100 Subject: [PATCH 14/20] Update doc/architecture.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Fermín Galán Márquez --- doc/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/architecture.md b/doc/architecture.md index 147df2931..0f3f3bff3 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -222,7 +222,7 @@ This timestamp is represented as two different properties of the mapped entity:: ISO8601 timestamp when the last measurement received from the device was observed. If no information about the measurement timestamp is received by the IoT Agent, the arrival time of the measurement will -be used to generate a `TimeInstant` for both the property and the attribute metadata. +be used to generate a `TimeInstant` for both the entity attribute and the attribute metadata. Take into account that: From 5aa3a0b234212fa00d389469adf99b8fb63b0c6d Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Tue, 3 Mar 2020 10:38:59 +0100 Subject: [PATCH 15/20] Update doc/architecture.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Fermín Galán Márquez --- doc/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/architecture.md b/doc/architecture.md index 0f3f3bff3..52f725b59 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -218,7 +218,7 @@ This timestamp is represented as two different properties of the mapped entity:: ISO8601 timestamp when the associated measurement (represented as attribute value) was observed. With NGSI-LD, the Standard `observedAt` metadata attribute is used instead. -- For NGSIv1/NGSI-v2 only, an additional propert called `TimeInstant` is added to the entity which captures as an +- For NGSIv1/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. If no information about the measurement timestamp is received by the IoT Agent, the arrival time of the measurement will From 03bb0fe8f7a53c566d8c2941f57d7cfde75174ae Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Tue, 3 Mar 2020 10:39:13 +0100 Subject: [PATCH 16/20] Update doc/installationguide.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Fermín Galán Márquez --- doc/installationguide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/installationguide.md b/doc/installationguide.md index 790140386..005caf5fa 100644 --- a/doc/installationguide.md +++ b/doc/installationguide.md @@ -209,7 +209,7 @@ used for the same purpose. For instance: - **timestamp**: if this flag is activated: - For NGSIv1/NGSIv2, 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 provision. - - With NGSI-LD, the standard `observedAt` metadata attribute is created instead. + - 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). - **defaultKey**: default string to use as API Key for devices that do not belong to a particular Configuration. From 6cd5300eeead5bf62a310f077ab9a8c3474a3d38 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Tue, 3 Mar 2020 10:39:39 +0100 Subject: [PATCH 17/20] Update doc/api.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Fermín Galán Márquez --- doc/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index 3893a50f0..0b00c73f2 100644 --- a/doc/api.md +++ b/doc/api.md @@ -211,7 +211,7 @@ the API resource fields and the same fields in the database model. | `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 addthe `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` metadata attribute is created instead. | true | +| `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 | From 16455aae58ed8b45953087e2965b63085311b4fb Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Tue, 3 Mar 2020 10:39:53 +0100 Subject: [PATCH 18/20] Update doc/architecture.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Fermín Galán Márquez --- doc/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/architecture.md b/doc/architecture.md index 52f725b59..d172617dd 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -216,7 +216,7 @@ This timestamp is represented as two different properties of the mapped entity:: - With NGSIv1/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` metadata attribute is used instead. + Standard `observedAt` property-of-a-property is used instead. - For NGSIv1/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. From 79918638c549130d7f8e19a3d9f6f32eb0ebccb0 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Tue, 3 Mar 2020 10:40:21 +0100 Subject: [PATCH 19/20] Update doc/advanced-topics.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Fermín Galán Márquez --- doc/advanced-topics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/advanced-topics.md b/doc/advanced-topics.md index b53073a58..2022598e8 100644 --- a/doc/advanced-topics.md +++ b/doc/advanced-topics.md @@ -212,7 +212,7 @@ events in the IoT Agent with the configured type name will be marked as events. This plugin processes the entity attributes looking for a `TimeInstant` attribute. If one is found, for NGSI-v1/NGSIv2, the plugin adds a `TimeInstant` attribute as metadata for every other attribute in the same request. With NGSI-LD, the -Standard `observedAt` metadata attribute is used instead. +Standard `observedAt` property-of-a-property is used instead. ##### Expression Translation plugin (expressionTransformation) From 3d72455f607b01caaae61b0f0208cb7f7dea2377 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Tue, 3 Mar 2020 10:54:59 +0100 Subject: [PATCH 20/20] Remove dead code - replace with comment --- lib/services/ngsi/ngsiService.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index b41329bfd..bd54ff239 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -688,13 +688,11 @@ function formatAsNGSILD(json){ obj[key] = json[key]; break; case constants.TIMESTAMP_ATTRIBUTE: - /* - var timestamp = json[constants.TIMESTAMP_ATTRIBUTE].value; - if(timestamp === constants.ATTRIBUTE_DEFAULT || !(moment(timestamp).isValid())){ - obj.observedAt = constants.DATETIME_DEFAULT; - } else { - obj.observedAt = moment.tz(timestamp, 'Etc/UTC').toISOString(); - }*/ + /* + The NGSIv2 timestamp attribute (if it exists) should not be processed here + as it will be removed. The equivalent NGSI-LD observedAt is treated as a simple + string, not a standard NGSI-LD Property. + */ break; default: obj[key] = convertNGSIv2ToLD(json[key]);