UNPKG

iotagent-node-lib

Version:

IoT Agent library to interface with NGSI Context Broker

316 lines (293 loc) 13.7 kB
/* * 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::daniel.moranjimenez@telefonica.com * * Modified by: Federico M. Facca - Martel Innovate * Modified by: Daniel Calvo - ATOS Research & Innovation */ const async = require('async'); const apply = async.apply; const statsRegistry = require('../stats/statsRegistry'); const deviceService = require('../devices/deviceService'); const intoTrans = require('../common/domain').intoTrans; const fillService = require('./../common/domain').fillService; const errors = require('../../errors'); const config = require('../../commonConfig'); const constants = require('../../constants'); const logger = require('logops'); const ngsiUtils = require('./ngsiUtils'); const _ = require('underscore'); const context = { op: 'IoTAgentNGSI.NGSIService' }; const attributeList = ['trust', 'cbHost', 'ngsiVersion']; let entityHandler; /** * Loads the correct ngsiService handler based on the current config. */ function init() { switch (config.ngsiVersion()) { case 'ld': entityHandler = require('./entities-NGSI-LD'); break; case 'mixed': entityHandler = require('./entities-NGSI-mixed'); break; default: entityHandler = require('./entities-NGSI-v2'); } } /** * Makes an update in the Device's entity in the context broker, with the values given in the 'attributes' array. This * array should comply to the NGSI's attribute format. * * @param {String} entityName Name of the entity to register. * @param {Array} attributes Attribute array containing the values to update. * @param {Object} typeInformation Configuration information for the device. * @param {String} token User token to identify against the PEP Proxies (optional). */ function sendUpdateValue(entityName, attributes, typeInformation, token, callback) { const newCallback = statsRegistry.withStats('updateEntityRequestsOk', 'updateEntityRequestsError', callback); const additionalCallback = (data, next) => { if (typeInformation.oldCtxt) { logger.debug(context, 'StoreOldCtxt %j', typeInformation.oldCtxt); deviceService.storeDeviceField('oldCtxt', typeInformation.oldCtxt, typeInformation, function () { next(null, data); }); } else { next(null, data); } }; const wrappedNewCallback = (err, result) => { if (err) { newCallback(err); } else { additionalCallback(result, (additionalErr, modifiedResult) => { if (additionalErr) { newCallback(additionalErr); } newCallback(null, modifiedResult || result); }); } }; // check config about store last measure if (typeInformation.storeLastMeasure) { logger.debug(context, 'StoreLastMeasure for %j', typeInformation); let originalMeasure = typeInformation.originalMeasure ? typeInformation.originalMeasure : null; deviceService.storeDeviceField('lastMeasure', originalMeasure, typeInformation, function () { delete typeInformation.originalMeasure; return entityHandler.sendUpdateValue(entityName, attributes, typeInformation, token, wrappedNewCallback); }); } else { entityHandler.sendUpdateValue(entityName, attributes, typeInformation, token, wrappedNewCallback); } } /** * Makes a query to the Device's entity in the context broker, with the list of attributes given by the 'attributes' * array. * * @param {String} entityName Name of the entity to query. * @param {Array} attributes Attribute array containing the names of the attributes to query. * @param {Object} typeInformation Configuration information for the device. * @param {String} token User token to identify against the PEP Proxies (optional). */ function sendQueryValue(entityName, attributes, typeInformation, token, callback) { entityHandler.sendQueryValue(entityName, attributes, typeInformation, token, callback); } /** * Update the trust for a device or a deviceGroup depending on the source of the trust token. * Currently is a placeholder, but it is needed in case of Auth services which tokens have * expiration. * * @param {Object} deviceGroup The deviceGroup where the trust token was stored. Null if stored in actual device. * @param {Object} deviceInformation The device where the trust token was stored. Null if stored in deviceGroup. * @param {String} trust The current trust token. * @param {Object} response The response where the new token is stored. * @param {Function} callback The callback function. */ function updateTrust(deviceGroup, deviceInformation, trust, response, callback) { if (deviceGroup && response && response.body && !config.getConfig().authentication.permanentToken) { const body = JSON.parse(response.body); if (body && body.refresh_token) { deviceGroup.trust = body.refresh_token; /* eslint-disable-next-line no-unused-vars */ config.getGroupRegistry().update(deviceGroup._id, deviceGroup, function (error, cb) { callback(null, response); }); } else { callback(null, response); } } else { callback(null, response); } } /** * Launches an operation against the Context Broker, getting the security token in case the authorization sequence is * enabled. This method can be invoked with an externally added deviceInformation object to overwrite the information * on the configuration (for preregistered devices). * * @param {Function} operationFunction Function to execute once the credentials and device information were retrieved. * @return {Function} The function that gets all the information wrapping the given operation. */ function executeWithDeviceInformation(operationFunction) { return function (entityName, type, apikey, attributes, deviceInformation, callback) { fillService(context, deviceInformation); logger.debug( context, 'executeWithDeviceInfo entityName %s type %s apikey %s attributes %j deviceInformation %j', entityName, type, apikey, attributes, deviceInformation ); const currentType = type ? type : deviceInformation.type; function getConfiguration(currentType, deviceInformation, callback) { config .getGroupRegistry() .getSilently(deviceInformation.resource, deviceInformation.apikey, function (error, deviceGroup) { if (error) { config.getGroupRegistry().getTypeSilently(currentType, function (error, deviceGroup) { callback(error, deviceGroup); }); } else { callback(null, deviceGroup); } }); } getConfiguration(currentType, deviceInformation, function (error, deviceGroup) { let typeInformation; const configDeviceInfo = config.getConfig().types[currentType]; if (error) { logger.debug(context, 'error %j in get group device', error); } // For anonymous devices use the typeInformation from the provisioned group and/or config directly. // For preregistered devices, augment the existing deviceInformation with selected attributes. if (!callback) { callback = deviceInformation; typeInformation = deviceGroup || { ...config.getConfigForTypeInformation(), ...configDeviceInfo }; } else { typeInformation = { ...config.getConfigForTypeInformation(), ...deviceInformation }; attributeList.forEach((key) => { typeInformation[key] = typeInformation[key] || (deviceGroup || {})[key] || (configDeviceInfo || {})[key]; }); } if (config.getConfig().authentication && config.getConfig().authentication.enabled) { const security = config.getSecurityService(); if (typeInformation && typeInformation.trust) { async.waterfall( [ apply(security.auth, typeInformation.trust), apply(updateTrust, deviceGroup, deviceInformation, typeInformation.trust), apply(security.getToken, typeInformation.trust), apply(operationFunction, entityName, attributes, typeInformation) ], callback ); } else { callback(new errors.SecurityInformationMissing(typeInformation.type)); } } else { operationFunction(entityName, attributes, typeInformation, null, callback); } }); }; } /** * Update the result of a command in the Context Broker. The result of the command has two components: the result * of the command itself will be represented with the sufix '_info' in the entity while the status is updated in the * attribute with the '_status' sufix. * * @param {String} entityName Name of the entity holding the command. * @param {String} resource Resource name of the endpoint the device is calling. * @param {String} apikey Apikey the device is using to send the values. * @param {String} commandName Name of the command whose result is being updated. * @param {String} commandResult Result of the command in string format. * @param {Object} deviceInformation Device information, including security and service information. (optional). */ function setCommandResult( entityName, resource, apikey, commandName, commandResult, status, deviceInformation, callback ) { config.getGroupRegistry().get(resource, apikey, function (error, deviceGroup) { let typeInformation; let commandInfo; const attributes = [ { name: commandName + constants.COMMAND_STATUS_SUFIX, type: constants.COMMAND_STATUS, value: status }, { name: commandName + constants.COMMAND_RESULT_SUFIX, type: constants.COMMAND_RESULT, value: commandResult } ]; // For anonymous devices use the typeInformation from the provisioned group and/or config directly. // For preregistered devices, augment the existing deviceInformation with selected attributes. if (!callback) { callback = deviceInformation; typeInformation = deviceGroup || config.getConfig().types[resource]; } else { typeInformation = deviceInformation; } // Ensure type, servce and subservice are always set, using fallbacks as necessary. typeInformation.type = typeInformation.type || (deviceGroup || {}).type || resource; typeInformation.service = typeInformation.service || config.getConfig().service; typeInformation.subservice = typeInformation.subservice || config.getConfig().subservice; commandInfo = _.where(typeInformation.commands, { name: commandName }); if (deviceGroup && commandInfo.length !== 1) { commandInfo = _.where(deviceGroup.commands, { name: commandName }); } if (commandInfo.length === 1) { exports.update(entityName, typeInformation.type, apikey, attributes, typeInformation, callback); } else { callback(new errors.CommandNotFound(commandName, typeInformation)); } }); } function addUpdateMiddleware(middleware) { ngsiUtils.updateMiddleware.push(middleware); } function addQueryMiddleware(middleware) { ngsiUtils.queryMiddleware.push(middleware); } function resetMiddlewares(callback) { ngsiUtils.updateMiddleware = []; ngsiUtils.queryMiddleware = []; callback(); } exports.update = intoTrans(context, executeWithDeviceInformation)(sendUpdateValue); exports.query = intoTrans(context, executeWithDeviceInformation)(sendQueryValue); exports.addUpdateMiddleware = intoTrans(context, addUpdateMiddleware); exports.addQueryMiddleware = intoTrans(context, addQueryMiddleware); exports.resetMiddlewares = intoTrans(context, resetMiddlewares); exports.setCommandResult = intoTrans(context, setCommandResult); exports.updateTrust = updateTrust; exports.init = init;