barnacles
Version:
Efficient real-time processor of ambient IoT data, enabling hyperlocal context as standard JSON. We believe in an open Internet of Things.
311 lines (263 loc) • 9.68 kB
JavaScript
/**
* Copyright reelyActive 2020-2025
* We believe in an open Internet of Things
*/
const Raddec = require('raddec');
const Device = require('./device');
const DEFAULT_DELAY_MILLISECONDS = 1000;
const DEFAULT_DECODING_COMPILATION_MILLISECONDS = 2000;
const DEFAULT_PACKET_COMPILATION_MILLISECONDS = 5000;
const DEFAULT_KEEP_ALIVE_MILLISECONDS = 5000;
const DEFAULT_HISTORY_MILLISECONDS = DEFAULT_KEEP_ALIVE_MILLISECONDS +
DEFAULT_DELAY_MILLISECONDS +
DEFAULT_DECODING_COMPILATION_MILLISECONDS;
const DEFAULT_DISAPPEARANCE_MILLISECONDS = 15000;
const DEFAULT_OBSERVED_EVENTS = [
Raddec.events.APPEARANCE,
Raddec.events.DISPLACEMENT,
Raddec.events.PACKETS,
Raddec.events.KEEPALIVE
];
const MIN_DEPTH = 1;
const DEFAULT_DEPTH = 2;
const MAX_DEPTH = 2; // TODO: support greater depth in future?
/**
* MapManager Class
* Manages a JavaScript Map instance.
*/
class MapManager {
/**
* MapManager constructor
* @param {Object} options The options as a JSON object.
* @constructor
*/
constructor(options) {
options = options || {};
this.parameters = createParameters(options);
this.store = new Map();
}
/**
* Retrieve the most recent data regarding all active/specified devices.
* @param {String} deviceId The device identifier.
* @param {Number} deviceIdType The device identifier type.
* @param {Array} properties The optional properties to include.
* @param {callback} callback Function to call on completion.
*/
retrieveDevices(deviceId, deviceIdType, properties, callback) {
let self = this;
let devices = {};
let isSpecificDevice = (deviceId && deviceIdType);
let isSpecificProperties = Array.isArray(properties) &&
(properties.length > 0);
if(isSpecificDevice) {
let signature = deviceId + Raddec.identifiers.SIGNATURE_SEPARATOR +
deviceIdType;
let isDevicePresent = self.store.has(signature);
if(isDevicePresent) {
let device = self.store.get(signature);
devices[signature] = device.assemble(properties);
return callback(devices);
}
}
else {
for(const signature of self.store.keys()) {
if(isSpecificProperties) {
let device = self.store.get(signature);
devices[signature] = device.assemble(properties);
}
else {
devices[signature] = {};
}
}
return callback(devices);
}
return callback(null);
}
/**
* Retrieve the most recent context regarding all active/specified devices.
* @param {Array} signatures The deviceId signatures to query.
* @param {Number} depth The (optional) depth of context to retrieve.
* @param {callback} callback Function to call on completion.
*/
retrieveContext(signatures, depth, callback) {
let self = this;
let devices = {};
let deviceNetwork = new Map();
let currentDepth = 0;
let isAllDevices = !signatures ||
(Array.isArray(signatures) && (signatures.length === 0));
if(!Number.isInteger(depth) || (depth < MIN_DEPTH) || (depth > MAX_DEPTH)) {
depth = DEFAULT_DEPTH;
}
// Step 1: iterate ALL the devices, collecting active first-level devices
for(const signature of self.store.keys()) {
let device = self.store.get(signature);
if(isAllDevices || device.hasIdOrNearest(signatures)) {
devices[signature] = device.assembleContext();
if(devices[signature].nearest) {
devices[signature].nearest.forEach((item) => {
deviceNetwork.set(item.device, currentDepth);
});
}
}
}
// Step 2: retrieve the devices nearest to the active first-level devices
if(depth > 1) {
deviceNetwork.forEach((value, signature) => {
let isIncluded = devices.hasOwnProperty(signature);
if(!isIncluded) {
let isDevicePresent = self.store.has(signature);
if(isDevicePresent) {
let device = self.store.get(signature);
devices[signature] = device.assembleContext();
}
else {
devices[signature] = {};
}
}
});
}
// TODO: implement depth beyond 2?
let isNoneFound = (Object.keys(devices).length === 0);
if(!isAllDevices && isNoneFound) {
return callback(null);
}
return callback(devices);
}
/**
* Insert the given raddec.
* @param {Raddec} raddec The given Raddec instance.
* @param {function} handleEvent The function to call if event is triggered.
*/
insertRaddec(raddec, handleEvent) {
let self = this;
let device;
let isDevicePresent = self.store.has(raddec.signature);
if(isDevicePresent) {
device = self.store.get(raddec.signature);
}
else {
device = new Device(raddec.transmitterId, raddec.transmitterIdType,
self.parameters);
self.store.set(raddec.signature, device);
}
device.handleRaddec(raddec, self.parameters);
}
/**
* Insert the given dynamb.
* @param {Object} dynamb The given dynamb.
*/
insertDynamb(dynamb) {
let self = this;
let device;
let signature = dynamb.deviceId + Raddec.identifiers.SIGNATURE_SEPARATOR +
dynamb.deviceIdType;
let isDevicePresent = self.store.has(signature);
if(isDevicePresent) {
device = self.store.get(signature);
}
else {
device = new Device(dynamb.deviceId, dynamb.deviceIdType,
self.parameters);
self.store.set(signature, device);
}
device.handleDynamb(dynamb, self.parameters);
}
/**
* Insert the given statid.
* @param {Object} statid The given statid.
*/
insertStatid(statid) {
let self = this;
let signature = statid.deviceId + Raddec.identifiers.SIGNATURE_SEPARATOR +
statid.deviceIdType;
let isDevicePresent = self.store.has(signature);
if(isDevicePresent) {
let device = self.store.get(signature);
device.handleStatid(statid);
}
}
/**
* Determine if there are events to handle.
* @param {function} handleEvent The function to call for each event.
* @param {function} callback The function to call on completion.
*/
determineEvents(handleEvent, callback) {
let self = this;
let currentTime = new Date().getTime();
let nextTimeout = currentTime + this.parameters.delayMilliseconds;
this.store.forEach((device, transmitterSignature) => {
let timeout = device.determineEvents(self.parameters, handleEvent);
let isDisappearance = (timeout < 0);
if(isDisappearance) {
self.store.delete(transmitterSignature);
}
else if(timeout < nextTimeout) {
nextTimeout = timeout;
}
});
return callback(nextTimeout);
}
/**
* Determine the receivers and their stats.
* @param {callback} callback Function to call on completion.
*/
determineReceivers(callback) {
let self = this;
let receivers = new Map();
self.store.forEach((device) => {
if(Array.isArray(device.latestRaddecEvent?.rssiSignature)) {
device.latestRaddecEvent.rssiSignature.forEach((entry, index) => {
let signature = entry.receiverId +
Raddec.identifiers.SIGNATURE_SEPARATOR +
entry.receiverIdType;
let isStrongest = (index === 0);
let receiver = receivers.get(signature);
if(!receivers.has(signature)) {
receiver = { receiverId: entry.receiverId,
receiverIdType: entry.receiverIdType,
numberOfReceivedDevices: 0,
numberOfStrongestReceivedDevices: 0 };
receivers.set(signature, receiver);
}
receiver.numberOfReceivedDevices++;
if(isStrongest) {
receiver.numberOfStrongestReceivedDevices++;
}
});
}
});
return callback(receivers);
}
}
/**
* Create from the given options the parameters for determining and preparing
* events.
* @param {Object} options The options as a JSON object.
*/
function createParameters(options) {
let delayMilliseconds = options.delayMilliseconds ||
DEFAULT_DELAY_MILLISECONDS;
let decodingCompilationMilliseconds =
options.decodingCompilationMilliseconds ||
DEFAULT_DECODING_COMPILATION_MILLISECONDS;
let packetCompilationMilliseconds = options.packetCompilationMilliseconds ||
DEFAULT_PACKET_COMPILATION_MILLISECONDS;
let historyMilliseconds = options.historyMilliseconds ||
DEFAULT_HISTORY_MILLISECONDS;
let keepAliveMilliseconds = options.keepAliveMilliseconds ||
DEFAULT_KEEP_ALIVE_MILLISECONDS;
let disappearanceMilliseconds = options.disappearanceMilliseconds ||
DEFAULT_DISAPPEARANCE_MILLISECONDS;
let observedEvents = options.observedEvents || DEFAULT_OBSERVED_EVENTS;
return {
delayMilliseconds: delayMilliseconds,
decodingCompilationMilliseconds: decodingCompilationMilliseconds,
packetCompilationMilliseconds: packetCompilationMilliseconds,
historyMilliseconds: historyMilliseconds,
keepAliveMilliseconds: keepAliveMilliseconds,
disappearanceMilliseconds: disappearanceMilliseconds,
observedEvents: observedEvents
};
}
module.exports = MapManager;