iotagent-node-lib
Version:
IoT Agent library to interface with NGSI Context Broker
767 lines (708 loc) • 32.2 kB
JavaScript
/*
* Copyright 2015 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
*/
/* eslint-disable no-prototype-builtins */
/* eslint-disable prefer-spread */
/* eslint-disable prefer-rest-params */
/* eslint-disable consistent-return */
const async = require('async');
const apply = async.apply;
const intoTrans = require('../common/domain').intoTrans;
const groupService = require('../groups/groupService');
const errors = require('../../errors');
const logger = require('logops');
const config = require('../../commonConfig');
const registrationUtils = require('./registrationUtils');
const subscriptions = require('../ngsi/subscriptionService');
const expressionPlugin = require('./../../plugins/expressionPlugin');
const pluginUtils = require('./../../plugins/pluginUtils');
const alarms = require('../common/alarmManagement');
const constants = require('../../constants');
const _ = require('underscore');
const context = {
op: 'IoTAgentNGSI.DeviceService'
};
let deviceHandler;
/**
* Loads the correct device handler based on the current config.
*/
function init() {
switch (config.ngsiVersion()) {
case 'ld':
deviceHandler = require('./devices-NGSI-LD');
break;
case 'mixed':
deviceHandler = require('./devices-NGSI-mixed');
break;
default:
deviceHandler = require('./devices-NGSI-v2');
}
}
/**
* Creates the initial entity representing the device in the Context Broker. This is important mainly to allow the
* rest of the updateContext operations to be performed using an UPDATE action instead of an APPEND one.
*
* @param {Object} deviceData Object containing all the deviceData needed to send the registration.
* @param {Object} newDevice Device object that will be stored in the database.
*/
function createInitialEntity(deviceData, newDevice, callback) {
deviceHandler.createInitialEntity(deviceData, newDevice, callback);
}
/**
* If the object_id or the name of the attribute is missing, complete it with the other piece of data.
*
* @param {Object} attribute Device attribute
* @return {*} Completed attribute
*/
function setDefaultAttributeIds(attribute) {
/* jshint camelcase: false */
if (!attribute.object_id && attribute.name) {
attribute.object_id = attribute.name;
}
if (!attribute.name && attribute.object_id) {
attribute.name = attribute.object_id;
}
return attribute;
}
/**
* Merge array of attributes coming from the device with another one coming from a configuration. The latter will
* complete the information in the former if the attribute in the configuration:
* - does not have a conflicting object_id with any attribute of the device.
* - does not have a conflicting name with any other attribute in the device
*
* @param {Array} original List of attributes of the device.
* @param {Array} newArray List of attributes of the configuration.
* @return {Array} Merge of the attributes of the device and those of the configuration.
*/
function mergeArrays(original, newArray) {
/* jshint camelcase: false */
const originalKeys = _.pluck(original, 'object_id');
const newKeys = _.pluck(newArray, 'object_id');
const addedKeys = _.difference(newKeys, originalKeys);
const differenceArray = newArray.filter(function (item) {
return item.object_id && addedKeys.indexOf(item.object_id) >= 0;
});
const originalNames = _.pluck(original, 'name');
const newNames = _.pluck(newArray, 'name');
const addedNames = _.difference(newNames, originalNames);
const differenceNamesArray = newArray.filter(function (item) {
return addedNames.indexOf(item.name) >= 0 && (!item.object_id || newKeys.indexOf(item.object_id) < 0);
});
return original.concat(differenceArray).concat(differenceNamesArray);
}
/**
* Complete the information of the device with the information in the configuration group (with precedence of the
* device). The first argument indicates what fields would be merged.
*
* @param {Object} fields Fields that will be merged.
* @param {Object} deviceData Device data.
* @param {Object} configuration Configuration data.
*/
function mergeDeviceWithConfiguration(fields, defaults, deviceData, configuration, callback) {
logger.debug(
context,
'deviceData before merge with conf: %j defaults: %j fields: %j configuration %j',
deviceData,
defaults,
fields,
configuration
);
for (let i = 0; i < fields.length; i++) {
const confField = fields[i] === 'active' ? 'attributes' : fields[i];
if (deviceData && deviceData[fields[i]] && ['active', 'lazy', 'commands'].indexOf(fields[i]) >= 0) {
deviceData[fields[i]] = deviceData[fields[i]].map(setDefaultAttributeIds);
} else if (deviceData && deviceData[fields[i]] && ['internalAttributes'].indexOf(fields[i]) >= 0) {
if (!(deviceData[fields[i]] instanceof Array)) {
deviceData[fields[i]] = [deviceData[fields[i]]];
}
}
if (configuration && configuration[confField] && ['attributes', 'lazy', 'commands'].indexOf(confField) >= 0) {
configuration[confField] = configuration[confField].map(setDefaultAttributeIds);
} else if (configuration && configuration[confField] && ['internalAttributes'].indexOf(confField) >= 0) {
if (!(configuration[confField] instanceof Array)) {
configuration[confField] = [configuration[confField]];
}
}
if (deviceData[fields[i]] && configuration && configuration[confField]) {
deviceData[fields[i]] = mergeArrays(deviceData[fields[i]], configuration[confField]);
} else if (
!deviceData[fields[i]] &&
configuration &&
confField in configuration &&
configuration[confField] !== undefined
) {
deviceData[fields[i]] = configuration[confField];
} else if (!deviceData[fields[i]] && (!configuration || !configuration[confField])) {
deviceData[fields[i]] = defaults[i];
}
}
if (configuration && configuration.cbHost) {
deviceData.cbHost = configuration.cbHost;
}
if (configuration && configuration.ngsiVersion) {
deviceData.ngsiVersion = configuration.ngsiVersion;
}
if (configuration && configuration.explicitAttrs !== undefined && deviceData.explicitAttrs === undefined) {
deviceData.explicitAttrs = configuration.explicitAttrs;
}
if (configuration && configuration.entityNameExp !== undefined) {
deviceData.entityNameExp = configuration.entityNameExp;
}
if (configuration && configuration.timestamp !== undefined && deviceData.timestamp === undefined) {
deviceData.timestamp = configuration.timestamp;
}
if (configuration && configuration.payloadType !== undefined && deviceData.payloadType === undefined) {
deviceData.payloadType = configuration.payloadType;
}
if (configuration && configuration.useCBflowControl !== undefined && deviceData.useCBflowControl === undefined) {
deviceData.useCBflowControl = configuration.useCBflowControl;
}
if (configuration && configuration.storeLastMeasure !== undefined && deviceData.storeLastMeasure === undefined) {
deviceData.storeLastMeasure = configuration.storeLastMeasure;
}
if (configuration && configuration.cmdMode !== undefined && deviceData.cmdMode === undefined) {
deviceData.cmdMode = configuration.cmdMode;
}
logger.debug(context, 'deviceData after merge with conf: %j', deviceData);
callback(null, deviceData);
}
/**
* Find the configuration group belonging to a given device
*
* @param {Object} deviceObj Device data.
*/
function findConfigurationGroup(deviceObj, callback) {
function handlerGroupFindByType(error, group) {
if (!group) {
config
.getGroupRegistry()
.findSilently(deviceObj.service, deviceObj.subservice, deviceObj.apikey, handlerGroupFind);
} else {
handlerGroupFind(error, group);
}
}
function handlerGroupFind(error, group) {
let effectiveGroup = group;
if (!group && config.getConfig().types[deviceObj.type]) {
effectiveGroup = config.getConfig().types[deviceObj.type];
} else if (!group) {
effectiveGroup = deviceObj;
}
callback(null, effectiveGroup);
}
config
.getGroupRegistry()
.findTypeSilently(
deviceObj.service,
deviceObj.subservice,
deviceObj.type,
deviceObj.apikey,
handlerGroupFindByType
);
}
/**
* Register a new device identified by the Id and Type in the Context Broker, and the internal registry.
*
* The device id and type are required fields for any registration. The rest of the parameters are optional, but, if
* they are not present in the function call arguments, the type must be registered in the configuration, so the
* service can infer their default values from the configured type. If an optional attribute is not given in the
* parameter list and there isn't a default configuration for the given type, a TypeNotFound error is raised.
*
* When an optional parameter is not included in the call, a null value must be given in its place.
*
* @param {Object} deviceObj Object with all the device information (mandatory).
*/
function registerDevice(deviceObj, callback) {
function checkDuplicates(deviceObj, innerCb) {
config.getRegistry().getSilently(
deviceObj.id,
deviceObj.apikey,
deviceObj.service,
deviceObj.subservice,
/* eslint-disable-next-line no-unused-vars */
function (error, device) {
if (!error) {
innerCb(new errors.DuplicateDeviceId(deviceObj), device);
} else {
innerCb();
}
}
);
}
function prepareDeviceData(deviceObj, configuration, callback) {
const deviceData = _.clone(deviceObj);
let selectedConfiguration;
logger.debug(context, 'Prepare device data:\n%s', JSON.stringify(deviceData, null, 4));
if (!deviceData.type) {
if (configuration && configuration.type) {
deviceData.type = configuration.type;
} else {
deviceData.type = config.getConfig().defaultType;
}
}
if ('explicitAttrs' in deviceData && deviceData.explicitAttrs === undefined) {
if (configuration && configuration.explicitAttrs !== undefined) {
deviceData.explicitAttrs = configuration.explicitAttrs;
} else {
deviceData.explicitAttrs = config.getConfig().explicitAttrs;
}
}
if (!deviceData.ngsiVersion) {
if (configuration && configuration.ngsiVersion) {
deviceData.ngsiVersion = configuration.ngsiVersion;
}
}
// Set polling and transport for autoprovisioned devices
if (!deviceData.transport && config.getConfig().defaultTransport) {
deviceData.transport =
configuration && configuration.transport
? configuration.transport
: config.getConfig().defaultTransport;
}
if (deviceData.transport === 'HTTP') {
if (deviceData.endpoint) {
deviceData.polling = false;
} else {
deviceData.polling = !(configuration && configuration.endpoint);
}
}
if (!deviceData.name) {
let entityName = null;
if (configuration && configuration.entityNameExp !== undefined && configuration.entityNameExp !== '') {
// Add device ID, TYPE, S, SS to the context for the JEXL expression
let attrList = pluginUtils.getIdTypeServSubServiceFromDevice(deviceData);
attrList = deviceData.staticAttributes ? attrList.concat(deviceData.staticAttributes) : attrList;
attrList = configuration.staticAttributes ? attrList.concat(configuration.staticAttributes) : attrList;
const ctxt = expressionPlugin.extractContext(attrList);
try {
entityName = expressionPlugin.applyExpression(configuration.entityNameExp, ctxt, deviceData);
} catch (e) {
logger.debug(
context,
'Error evaluating expression for entityName: %s with context: %s',
configuration.entityNameExp,
ctxt
);
}
}
deviceData.name = entityName ? entityName : defaultName();
}
if (!configuration && config.getConfig().types[deviceData.type]) {
selectedConfiguration = config.getConfig().types[deviceData.type];
} else {
selectedConfiguration = configuration;
}
callback(null, deviceData, selectedConfiguration);
function defaultName() {
let conjunction;
let name;
if (configuration && configuration.defaultEntityNameConjunction !== undefined) {
conjunction = configuration.defaultEntityNameConjunction;
} else {
conjunction = config.getConfig().defaultEntityNameConjunction;
}
name = deviceData.type + conjunction + deviceData.id;
if (config.checkNgsiLD(configuration)) {
name = 'urn:ngsi-ld:' + deviceData.type + conjunction + deviceData.id;
}
logger.debug(
context,
'Device name not found, falling back to deviceType%sdeviceId [%s]',
conjunction,
deviceData.name
);
return name;
}
}
function completeRegistrations(error, deviceData) {
if (error) {
return callback(error);
}
logger.debug(context, 'Registering device into NGSI Service:\n%s', JSON.stringify(deviceData, null, 4));
async.waterfall(
[
apply(registrationUtils.sendRegistrations, false, deviceData),
apply(registrationUtils.processContextRegistration, deviceData),
apply(createInitialEntity, deviceData)
],
function (error, results) {
if (error) {
callback(error);
} else {
deviceObj.registrationId = results.registrationId;
deviceObj.subscriptionId = results.subscriptionId;
deviceObj.name = deviceData.name;
deviceObj.service = deviceData.service;
deviceObj.subservice = deviceData.subservice;
deviceObj.type = deviceData.type;
deviceObj.staticAttributes = deviceObj.staticAttributes ? deviceObj.staticAttributes : [];
deviceObj.commands = deviceData.commands ? deviceData.commands : [];
deviceObj.lazy = deviceObj.lazy ? deviceObj.lazy : [];
if ('apikey' in deviceData && deviceData.apikey !== undefined) {
deviceObj.apikey = deviceData.apikey;
}
if ('transport' in deviceData && deviceData.transport !== undefined) {
deviceObj.transport = deviceData.transport;
}
if ('polling' in deviceData && deviceData.polling !== undefined) {
deviceObj.polling = deviceData.polling;
}
logger.debug(context, 'Storing device :\n%s', JSON.stringify(deviceObj, null, 4));
config.getRegistry().store(deviceObj, callback);
}
}
);
}
async.waterfall(
[
apply(checkDuplicates, deviceObj),
apply(findConfigurationGroup, deviceObj),
apply(prepareDeviceData, deviceObj),
apply(
mergeDeviceWithConfiguration,
['lazy', 'active', 'staticAttributes', 'commands', 'subscriptions'],
[null, null, [], [], [], [], []]
)
],
completeRegistrations
);
}
function removeAllSubscriptions(device, callback) {
function removeSubscription(subscription, callback) {
subscriptions.unsubscribe(device, subscription.id, callback);
}
if (device.subscriptions) {
async.map(device.subscriptions, removeSubscription, callback);
} else {
callback(null, {});
}
}
/**
* Unregister a device from the Context broker and the internal registry.
*
* @param {String} id Device ID of the device to register.
* @param {String} service Service of the device to unregister.
* @param {String} subservice Subservice inside the service for the unregisterd device.
*/
function unregisterDevice(id, apikey, service, subservice, callback) {
function processContextUnregister(body, innerCallback) {
innerCallback(null);
}
function processUnsubscribes(device, innerCallback) {
innerCallback(null);
}
logger.debug(context, 'Removing device %j %j %j %j register in Device Service', id, apikey, service, subservice);
config.getRegistry().get(id, apikey, service, subservice, function (error, device) {
if (error) {
callback(error);
} else {
async.waterfall(
[
apply(findConfigurationGroup, device),
apply(
mergeDeviceWithConfiguration,
['lazy', 'active', 'staticAttributes', 'commands', 'subscriptions'],
[null, null, [], [], [], [], []],
device
)
],
function (error, mergedDevice) {
if (error) {
callback(error);
} else {
async.waterfall(
[
apply(removeAllSubscriptions, mergedDevice),
processUnsubscribes,
apply(registrationUtils.sendRegistrations, true, mergedDevice),
processContextUnregister,
apply(config.getRegistry().remove, id, apikey, service, subservice)
],
callback
);
}
}
);
}
});
}
function updateRegisterDevice(deviceObj, previousDevice, entityInfoUpdated, callback) {
deviceHandler.updateRegisterDevice(deviceObj, previousDevice, entityInfoUpdated, callback);
}
/**
* Return a list of all the devices registered in the system. This function can be invoked in three different ways:
* with just one parameter (the callback) with three parameters (service, subservice and callback) or with five
* parameters (including limit and offset).
*
* @param {String} type 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 listDevicesWithType(type, service, subservice, limit, offset, callback) {
if (!callback) {
if (service && subservice && limit) {
callback = limit;
} else if (service) {
callback = service;
service = null;
subservice = null;
} else {
logger.fatal(context, "GENERAL-001: Couldn't find callback in listDevices() call.");
}
}
config.getRegistry().list(type, service, subservice, limit, offset, callback);
}
/**
* Return a list of all the devices registered in the system. This function can be invoked in three different ways:
* with just one parameter (the callback) with three parameters (service, subservice and callback) or with five
* parameters (including limit and offset).
*
* @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(service, subservice, limit, offset, callback) {
if (!callback) {
if (service && subservice && limit) {
callback = limit;
} else if (service) {
callback = service;
service = null;
subservice = null;
} else {
logger.fatal(context, "GENERAL-001: Couldn't find callback in listDevices() call.");
}
}
config.getRegistry().list(null, service, subservice, limit, offset, callback);
}
/**
* Retrieve a device from the device registry.
*
* @param {String} deviceId ID of the device to be found.
* @param {String} service Service for which the requested device.
* @param {String} subservice Subservice inside the service for which the device is requested.
*/
function getDevice(deviceId, apikey, service, subservice, callback) {
config.getRegistry().get(deviceId, apikey, service, subservice, callback);
}
/**
* Update a device from the device registry.
*
* @param {String} device JSON object contain the device to update.
*/
function updateDevice(device, callback) {
logger.debug(context, 'updateDevice %j', device);
config.getRegistry().update(device, device, callback);
}
/**
* Retrieve a device from the device registry, allowing not found it (will be created later)
*
* @param {String} deviceId ID of the device to be found.
* @param {String} service Service for which the requested device.
* @param {String} subservice Subservice inside the service for which the device is requested.
*/
function getDeviceSilently(deviceId, apikey, service, subservice, callback) {
config.getRegistry().getSilently(deviceId, apikey, service, subservice, callback);
}
/**
* Clear all the information in the registry.
*/
function clearRegistry(callback) {
config.getRegistry().clear(callback);
}
/**
* Retrieve a device from the registry based on its entity name.
*
* @param {String} deviceName Name of the entity associated to a device.
* @param {String} service Service the device belongs to.
* @param {String} subservice Division inside the service.
*/
function getDeviceByName(deviceName, service, subservice, callback) {
config.getRegistry().getByName(deviceName, service, subservice, callback);
}
/**
* Retrieve a device from the registry based on its entity name and type
*
* @param {String} deviceName Name of the entity associated to a device.
* @param {String} deviceType Type of the entity associated to a device.
* @param {String} service Service the device belongs to.
* @param {String} subservice Division inside the service.
*/
function getDeviceByNameAndType(deviceName, deviceType, service, subservice, callback) {
config.getRegistry().getByNameAndType(deviceName, deviceType, service, subservice, callback);
}
/**
* Retrieve a device from the registry based on the value of a given attribute.
*
* @param {String} attributeName Name of the attribute to perform the search with.
* @param {String} attributeValue Value of the attribute to perform the selection.
* @param {String} service Service the device belongs to.
* @param {String} subservice Division inside the service.
*/
function getDevicesByAttribute(attributeName, attributeValue, service, subservice, callback) {
config.getRegistry().getDevicesByAttribute(attributeName, attributeValue, service, subservice, callback);
}
/**
* Wraps a function, throwing an exception if the function is invoked before the registry is initialized.
*
* @param {Function} fn Original function to wrap.
* @return {Function} Wrapped function.
*/
function checkRegistry(fn) {
return function () {
const args = Array.prototype.slice.call(arguments);
const callbacks = args.slice(-1);
if (config.getRegistry()) {
fn.apply(null, args);
} else if (callbacks && callbacks.length === 1 && typeof callbacks[0] === 'function') {
logger.error(context, 'Tried to access device information before a registry was available');
callbacks[0](new errors.RegistryNotAvailable());
} else {
logger.error(context, 'Tried to access device information without providing a callback');
}
};
}
function findOrCreate(deviceId, apikey, group, callback) {
getDeviceSilently(deviceId, apikey, group.service, group.subservice, function (error, device) {
if (!error && device) {
if (
(!('apikey' in device) || device.apikey === undefined) &&
'apikey' in group &&
group.apikey !== undefined
) {
logger.info(context, 'Update provisioned device %j with measure/group apikey %j', device, group.apikey);
device.apikey = group.apikey; // group apikey is the same of current measure apikey
updateDevice(device, function (error) {
callback(error, device, group);
});
} else {
callback(null, device, group);
}
} else if (error.name === 'DEVICE_NOT_FOUND') {
const newDevice = {
id: deviceId,
apikey,
service: group.service,
subservice: group.subservice,
type: group.type
};
if (config.getConfig().iotManager && config.getConfig().iotManager.protocol) {
newDevice.protocol = config.getConfig().iotManager.protocol;
}
if ('ngsiVersion' in group && group.ngsiVersion !== undefined) {
newDevice.ngsiVersion = group.ngsiVersion;
}
if (
(!('apikey' in newDevice) || newDevice.apikey === undefined) &&
'apikey' in group &&
group.apikey !== undefined
) {
newDevice.apikey = group.apikey;
}
// Check autoprovision flag in order to register or not device
if (group.autoprovision === undefined || group.autoprovision === true) {
logger.debug(context, 'Registering autoprovision of Device %j for its conf %j', newDevice, group);
registerDevice(newDevice, function (error, device) {
if (error && error.name === 'DUPLICATE_DEVICE_ID') {
alarms.release(constants.MONGO_ALARM, error);
logger.warn(context, 'Error %j already registered autoprovisioned device: %j ', error, device);
callback(null, device, group);
} else {
logger.debug(context, 'registered autoprovisioned device: %j ', device);
callback(error, device, group);
}
});
} else {
logger.info(
context,
'Device %j not provisioned due autoprovision is disabled by its conf %j',
newDevice,
group
);
callback(new errors.DeviceNotFound(deviceId, newDevice));
}
} else {
callback(error);
}
});
}
/**
* Retrieve a device from the device repository based on the given APIKey and DeviceID, creating one if none is
* found for the given data.
*
* @param {String} deviceId Device ID of the device that wants to be retrieved or created.
* @param {String} apiKey APIKey of the Device Group (or default APIKey).
*/
function retrieveDevice(deviceId, apiKey, callback) {
if (apiKey === config.getConfig().defaultKey) {
getDevicesByAttribute('id', deviceId, null, null, function (error, devices) {
if (error) {
callback(error);
} else if (devices && devices.length === 1) {
callback(null, devices[0]);
} else {
logger.error(context, "Couldn't find device data for APIKey [%s] and DeviceId[%s]", deviceId, apiKey);
callback(new errors.DeviceNotFound(deviceId, { apikey: apiKey }));
}
});
} else {
async.waterfall(
[
apply(groupService.getSilently, config.getConfig().defaultResource || '', apiKey),
apply(findOrCreate, deviceId, apiKey),
apply(
mergeDeviceWithConfiguration,
['lazy', 'active', 'staticAttributes', 'commands', 'subscriptions'],
[null, null, [], [], [], [], []]
)
],
callback
);
}
}
function storeDeviceField(fieldName, fieldValue, typeInformation, callback) {
config.getRegistry().storeDeviceField(fieldName, fieldValue, typeInformation, callback);
}
exports.listDevices = intoTrans(context, checkRegistry)(listDevices);
exports.listDevicesWithType = intoTrans(context, checkRegistry)(listDevicesWithType);
exports.getDevice = intoTrans(context, checkRegistry)(getDevice);
exports.updateDevice = intoTrans(context, checkRegistry)(updateDevice);
exports.getDeviceSilently = intoTrans(context, checkRegistry)(getDeviceSilently);
exports.getDevicesByAttribute = intoTrans(context, checkRegistry)(getDevicesByAttribute);
exports.getDeviceByName = intoTrans(context, checkRegistry)(getDeviceByName);
exports.getDeviceByNameAndType = intoTrans(context, checkRegistry)(getDeviceByNameAndType);
exports.register = intoTrans(context, registerDevice);
exports.updateRegister = intoTrans(context, updateRegisterDevice);
exports.unregister = intoTrans(context, unregisterDevice);
exports.clearRegistry = intoTrans(context, checkRegistry)(clearRegistry);
exports.retrieveDevice = intoTrans(context, checkRegistry)(retrieveDevice);
exports.mergeDeviceWithConfiguration = mergeDeviceWithConfiguration;
exports.findOrCreate = findOrCreate;
exports.findConfigurationGroup = findConfigurationGroup;
exports.storeDeviceField = storeDeviceField;
exports.init = init;