iotagent-node-lib
Version:
IoT Agent library to interface with NGSI Context Broker
457 lines (421 loc) • 14.9 kB
JavaScript
/*
* 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
*/
const logger = require('logops');
const mongoose = require('mongoose');
const config = require('../../commonConfig');
const fillService = require('./../common/domain').fillService;
const alarmsInt = require('../common/alarmManagement').intercept;
const errors = require('../../errors');
const constants = require('../../constants');
const Device = require('../../model/Device');
const async = require('async');
let context = {
op: 'IoTAgentNGSI.MongoDBDeviceRegister'
};
const attributeList = [
'id',
'type',
'name',
'service',
'subservice',
'lazy',
'commands',
'staticAttributes',
'active',
'registrationId',
'internalId',
'internalAttributes',
'resource',
'apikey',
'protocol',
'endpoint',
'transport',
'polling',
'timestamp',
'explicitAttrs',
'ngsiVersion',
'subscriptions',
'payloadType',
'useCBflowControl',
'storeLastMeasure'
];
/**
* Create a new register for a device. The device object should contain the id, type and registrationId
*
* @param {Object} newDevice Device object to be stored
*/
function storeDevice(newDevice, callback) {
/* eslint-disable-next-line new-cap */
const deviceObj = new Device.model();
attributeList.forEach((key) => {
deviceObj[key] = newDevice[key];
});
// Ensure protocol is in newDevice
if (!newDevice.protocol && config.getConfig().iotManager && config.getConfig().iotManager.protocol) {
deviceObj.protocol = config.getConfig().iotManager.protocol;
}
logger.debug(context, 'Storing device with id [%s] and type [%s]', newDevice.id, newDevice.type);
deviceObj
.save({})
.then((deviceDAO) => {
callback(null, deviceDAO.toObject());
})
.catch((error) => {
if (error.code === 11000) {
logger.debug(context, 'Tried to insert a device with duplicate ID in the database: %s', error);
callback(new errors.DuplicateDeviceId(newDevice), newDevice);
} else {
logger.debug(context, 'Error storing device information: %s', error);
callback(new errors.InternalDbError(error));
}
});
}
/**
* Remove the device identified by its id and service.
*
* @param {String} id Device ID of the device to remove.
* @param {String} service Service of the device to remove.
* @param {String} subservice Subservice inside the service for the removed device.
*/
function removeDevice(id, apikey, service, subservice, callback) {
const condition = {
id,
service,
subservice
};
if (apikey) {
condition.apikey = apikey;
}
logger.debug(context, 'Removing device with id [%s]', id);
const query = Device.model.deleteOne(condition);
query
.exec({})
.then(() => {
logger.debug(context, 'Device [%s] successfully removed.', id);
callback(null);
})
.catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting device: %s', error);
callback(new errors.InternalDbError(error));
});
}
/**
* Return the list of currently registered devices (via callback).
*
* @param {String} tyoe Type for which the devices are requested.
* @param {String} service Service for which the devices are requested.
* @param {String} subservice Subservice inside the service for which the devices are requested.
* @param {Number} limit Maximum number of entries to return.
* @param {Number} offset Number of entries to skip for pagination.
*/
function listDevices(type, service, subservice, limit, offset, callback) {
const condition = {};
if (type) {
condition.type = type;
}
if (service) {
condition.service = service;
}
if (subservice) {
condition.subservice = subservice;
}
const query = Device.model.find(condition).sort();
const queryCount = Device.model.countDocuments(condition);
if (limit) {
query.limit(parseInt(limit, 10));
}
if (offset) {
query.skip(parseInt(offset, 10));
}
function funcQuery(cb) {
query
.exec({})
.then((res) => {
cb(null, res);
})
.catch((error) => {
cb(error);
});
}
function funcQueryCount(cb) {
queryCount
.exec({})
.then((res) => {
cb(null, res);
})
.catch((error) => {
cb(error);
});
}
async.series([funcQuery, funcQueryCount], function (error, results) {
callback(error, {
count: results[1],
devices: results[0]
});
});
}
function findOneInMongoDB(queryParams, id, callback) {
const query = Device.model.findOne(queryParams);
query.select({ __v: 0 });
query.lean();
query
.exec({})
.then((data) => {
if (data) {
context = fillService(context, data);
logger.debug(context, 'Device data found: %j', data);
callback(null, data);
} else {
logger.debug(context, 'Device [%s] not found.', id);
callback(new errors.DeviceNotFound(id, queryParams));
}
})
.catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting device: %s', error);
callback(new errors.InternalDbError(error));
});
}
/**
* Internal function used to find a device in the DB.
*
* @param {String} id ID of the Device to find.
* @param {String} Apikey Apikey of the Device to find.
* @param {String} service Service the device belongs to (optional).
* @param {String} subservice Division inside the service (optional).
*/
function getDeviceById(id, apikey, service, subservice, callback) {
const queryParams = {
id,
service,
subservice
};
if (apikey) {
queryParams.apikey = apikey;
}
context = fillService(context, queryParams);
logger.debug(context, 'Looking for device with id [%s] and queryParams [%j].', id, queryParams);
findOneInMongoDB(queryParams, id, callback);
}
/**
* Retrieves a device using it ID, converting it to a plain Object before calling the callback.
*
* @param {String} id ID of the Device to find.
* @param {String} service Service the device belongs to.
* @param {String} subservice Division inside the service.
*/
function getDevice(id, apikey, service, subservice, callback) {
getDeviceById(id, apikey, service, subservice, function (error, data) {
if (error) {
callback(error);
} else {
callback(null, data);
}
});
}
function getByNameAndType(name, type, service, servicepath, callback) {
context = fillService(context, { service, subservice: servicepath });
const optionsQuery = {
name,
service,
subservice: servicepath
};
if (type) {
optionsQuery.type = type;
}
logger.debug(context, 'Looking for device with [%j].', optionsQuery);
const query = Device.model.findOne(optionsQuery);
query.select({ __v: 0 });
query.lean();
query
.exec({})
.then((data) => {
if (data) {
context = fillService(context, data);
logger.debug(context, 'Device data found: %j', data);
callback(null, data);
} else {
logger.debug(context, 'Device [%s] not found.', name);
callback(new errors.DeviceNotFound(name, optionsQuery));
}
})
.catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting device: %s', error);
callback(new errors.InternalDbError(error));
});
}
function getByName(name, service, servicepath, callback) {
getByNameAndType(name, null, service, servicepath, callback);
}
/**
* Updates the given device into the database.
* updated.
*
* @param {Object} device Device object with the new values to write.
*/
function update(previousDevice, device, callback) {
logger.debug(context, 'Storing updated values for device [%s]:\n%s', device.id, JSON.stringify(device, null, 4));
getDevice(device.id, previousDevice.apikey, device.service, device.subservice, function (error, data) {
if (error) {
callback(error);
} else {
data.lazy = device.lazy;
data.active = device.active;
data.internalId = device.internalId;
data.staticAttributes = device.staticAttributes;
data.internalAttributes = device.internalAttributes;
data.commands = device.commands;
data.endpoint = device.endpoint;
data.transport = device.transport;
data.polling = device.polling;
data.name = device.name;
data.type = device.type;
data.apikey = device.apikey;
data.registrationId = device.registrationId;
data.explicitAttrs = device.explicitAttrs;
data.ngsiVersion = device.ngsiVersion;
data.timestamp = device.timestamp;
data.subscriptions = device.subscriptions;
data.payloadType = device.payloadType;
data.useCBflowControl = device.useCBflowControl;
data.storeLastMeasure = device.storeLastMeasure;
data.lastMeasure = device.lastMeasure;
/* eslint-disable-next-line new-cap */
const deviceObj = new Device.model(data);
deviceObj.isNew = false;
deviceObj
.save({})
.then((deviceDAO) => {
callback(null, deviceDAO.toObject());
})
.catch((error) => {
logger.debug(fillService(context, device), 'Error storing device information: %s', error);
callback(new errors.InternalDbError(error));
});
}
});
}
/**
* Cleans all the information in the database, leaving it in a clean state.
*/
function clear(callback) {
mongoose.connection
.dropDatabase()
.then(() => {
callback(null);
})
.catch((error) => {
callback(error);
});
}
function itemToObject(i) {
if (i.toObject) {
return i.toObject();
} else {
return i;
}
}
function getDevicesByAttribute(name, value, service, subservice, callback) {
const filter = {};
if (service) {
filter.service = service;
}
if (subservice) {
filter.subservice = subservice;
}
filter[name] = value;
context = fillService(context, filter);
logger.debug(context, 'Looking for device with filter [%j].', filter);
const query = Device.model.find(filter);
query.select({ __v: 0 });
query.lean();
query
.exec({})
.then((devices) => {
if (devices) {
callback(null, devices.map(itemToObject));
} else {
logger.debug(context, 'Device [%s] not found.', name);
callback(new errors.DeviceNotFound(name, filter));
}
})
.catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting device: %s', error);
callback(new errors.InternalDbError(error));
});
}
function storeDeviceField(fieldName, fieldValue, typeInformation, callback) {
if (
typeInformation &&
typeInformation.id &&
typeInformation.apikey &&
typeInformation.service &&
typeInformation.subservice
) {
getDevice(
typeInformation.id,
typeInformation.apikey,
typeInformation.service,
typeInformation.subservice,
function (error, data) {
if (error) {
callback(error);
} else {
if (fieldName === 'lastMeasure') {
data.lastMeasure = { timestamp: new Date().toISOString(), measure: fieldValue };
} else {
data[fieldName] = fieldValue;
}
/* eslint-disable-next-line new-cap */
const deviceObj = new Device.model(data);
deviceObj.isNew = false;
deviceObj
.save({})
.then((deviceDao) => {
callback(null, deviceDao.toObject());
})
.catch((error) => {
logger.debug(
fillService(context, deviceObj),
'Error storing device information: %s',
error
);
callback(new errors.InternalDbError(error));
});
}
}
);
} else {
callback(null, null);
}
}
exports.getDevicesByAttribute = alarmsInt(constants.MONGO_ALARM, getDevicesByAttribute);
exports.store = alarmsInt(constants.MONGO_ALARM, storeDevice);
exports.storeDeviceField = alarmsInt(constants.MONGO_ALARM, storeDeviceField);
exports.update = alarmsInt(constants.MONGO_ALARM, update);
exports.remove = alarmsInt(constants.MONGO_ALARM, removeDevice);
exports.list = alarmsInt(constants.MONGO_ALARM, listDevices);
exports.get = alarmsInt(constants.MONGO_ALARM, getDevice);
exports.getSilently = getDevice;
exports.getByName = alarmsInt(constants.MONGO_ALARM, getByName);
exports.getByNameAndType = alarmsInt(constants.MONGO_ALARM, getByNameAndType);
exports.clear = alarmsInt(constants.MONGO_ALARM, clear);