From 6f003fd848c784cf43bf085e6ee18d23215b8604 Mon Sep 17 00:00:00 2001 From: Pericles Alves Date: Thu, 7 Feb 2019 19:10:12 -0800 Subject: [PATCH] Adding timestamp field --- IoTCIntegration/index.js | 2 +- IoTCIntegration/lib/engine.js | 14 ++++++++++++-- README.md | 8 ++++++-- iotc-bridge-az-function.zip | Bin 6215 -> 6348 bytes 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/IoTCIntegration/index.js b/IoTCIntegration/index.js index 426ae92..c7697ad 100644 --- a/IoTCIntegration/index.js +++ b/IoTCIntegration/index.js @@ -18,7 +18,7 @@ let kvToken; module.exports = async function (context, req) { try { - await handleMessage({ ...parameters, log: context.log, getSecret: getKeyVaultSecret }, req.body.device, req.body.measurements); + await handleMessage({ ...parameters, log: context.log, getSecret: getKeyVaultSecret }, req.body.device, req.body.measurements, req.body.timestamp); } catch (e) { context.log('[ERROR]', e.message); diff --git a/IoTCIntegration/lib/engine.js b/IoTCIntegration/lib/engine.js index 37ff64e..489ff65 100644 --- a/IoTCIntegration/lib/engine.js +++ b/IoTCIntegration/lib/engine.js @@ -26,7 +26,7 @@ const deviceCache = {}; * @param {{ deviceId: string }} device * @param {{ [field: string]: number }} measurements */ -module.exports = async function (context, device, measurements) { +module.exports = async function (context, device, measurements, timestamp) { if (device) { if (!device.deviceId || !/^[a-z0-9\-]+$/.test(device.deviceId)) { throw new StatusError('Invalid format: deviceId must be alphanumeric, lowercase, and may contain hyphens.', 400); @@ -39,12 +39,22 @@ module.exports = async function (context, device, measurements) { throw new StatusError('Invalid format: invalid measurement list.', 400); } + if (timestamp && isNaN(Date.parse(timestamp))) { + throw new StatusError('Invalid format: if present, timestamp must be in ISO format (e.g., YYYY-MM-DDTHH:mm:ss.sssZ)', 400); + } + const client = Device.Client.fromConnectionString(await getDeviceConnectionString(context, device), DeviceTransport.Http); try { + const message = new Device.Message(JSON.stringify(measurements)); + + if (timestamp) { + message.properties.add('iothub-creation-time-utc', timestamp); + } + await util.promisify(client.open.bind(client))(); context.log('[HTTP] Sending telemetry for device', device.deviceId); - await util.promisify(client.sendEvent.bind(client))(new Device.Message(JSON.stringify(measurements))); + await util.promisify(client.sendEvent.bind(client))(message); await util.promisify(client.close.bind(client))(); } catch (e) { // If the device was deleted, we remove its cached connection string diff --git a/README.md b/README.md index fe03c74..7692e1a 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ In the console, run the command `npm install` (this command takes ~20 minutes to ![Get function URL](assets/getFunctionUrl.PNG "Get function URL") -Messages sent to the device bridge must have the following format in the Body: +Messages sent to the device bridge must have the following format in the body: ```json { "device": { @@ -49,6 +49,10 @@ Messages sent to the device bridge must have the following format in the Body: } ``` +An optional `timestamp` field can be included in the body, to specify the UTC date and time of the message. +This field must be in ISO format (e.g., YYYY-MM-DDTHH:mm:ss.sssZ). If `timestamp` is not provided, +the current date and time will be used. + > NOTE: `deviceId` must be alphanumeric, lowercase, and may contain hyphens. The values of the fields in `measurements` must be numbers or strings. 6. When a message with a new `deviceId` is sent to IoT Central by the device bridge, a device will be created as an **Unassociated device**. Unassociated devices appear in your IoT Central application in `Device Explorer > Unassociated devices`. Click `Associate` and choose a device template to start receiving incoming measurements from that device in IoT Central. @@ -246,7 +250,7 @@ Function. You can check the integrity of the code being deployed by verifying th of the `iotc-bridge-az-function.zip` file in the root of this repository matches the following: ``` -A55889E1808D666CE03A2A32C2B304F4DADAA6F72D599CA0D4A15C8666737E4F +4DE7C133828461E5C2E11125E7B22E12AA4E238BF19F4837E0D16950109DAF68 ``` # Contributing diff --git a/iotc-bridge-az-function.zip b/iotc-bridge-az-function.zip index 121dbe53098b96941a79aebec6b3c6b66a882256..fe8ad5956f4c1c27bf3a1a4fb5a4b00f847635d7 100644 GIT binary patch delta 3581 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