hlc-server
Version:
Serves real-time real-world context at a human scale by combining RFID, RTLS and M2M with structured, linked data on the web. We believe in an open Internet of Things.
351 lines (289 loc) • 10.4 kB
JavaScript
/**
* Copyright reelyActive 2019
* We believe in an open Internet of Things
*/
// Constants
const SIGNATURE_SEPARATOR = '/';
const RADDEC_HISTORY_LENGTH = 5;
const RDPS = ' / ';
const EVENT_ICONS = [
'fas fa-sign-in-alt',
'fas fa-route',
'fas fa-info',
'fas fa-heartbeat',
'fas fa-sign-out-alt'
];
// DOM elements
let idFilter = document.querySelector('#idFilter');
let idDropdown = document.querySelector('#idDropdown');
let resetButton = document.querySelector('#resetButton');
let selectButton = document.querySelector('#selectButton');
let raddecTableBody = document.querySelector('#raddecTableBody');
let rssiTableBody = document.querySelector('#rssiTableBody');
let txStoryVis = document.querySelector('#txStoryVis');
let txStoryJson = document.querySelector('#txStoryJson');
let rxStoryVis = document.querySelector('#rxStoryVis');
let rxStoryJson = document.querySelector('#rxStoryJson');
// Other variables
let idSignatures = [];
let selectedIdSignature;
let currentReceiverSignature;
let isSelected = false;
let baseUrl = window.location.protocol + '//' + window.location.hostname +
':' + window.location.port;
let raddecs = [];
// Handle optional search parameters
let params = new URLSearchParams(document.location.search.substring(1));
let searchTransmitterIdSignature = params.get('transmitterIdSignature');
if(searchTransmitterIdSignature) {
selectedIdSignature = searchTransmitterIdSignature;
idFilter.value = searchTransmitterIdSignature;
fetchAndUpdateStoryEntry();
}
// Connect to the socket.io stream and feed to beaver
let socket = io.connect(baseUrl);
beaver.listen(socket, true);
// Non-disappearance events
beaver.on([ 0, 1, 2, 3 ], function(raddec) {
let transmitterSignature = raddec.transmitterId +
SIGNATURE_SEPARATOR +
raddec.transmitterIdType;
addIdentifierSignature(transmitterSignature);
if(isSelected && (transmitterSignature === selectedIdSignature)) {
updateTransmitter(raddec);
updateReceiver(raddec);
}
});
// Disappearance events
beaver.on([ 4 ], function(raddec) {
let transmitterSignature = raddec.transmitterId +
SIGNATURE_SEPARATOR +
raddec.transmitterIdType;
removeIdentifierSignature(transmitterSignature);
if(isSelected && (transmitterSignature === selectedIdSignature)) {
updateTransmitter(raddec);
rxStoryVis.innerHTML = '';
rxStoryJson.innerHTML = '';
}
});
// Add the given identifier signature to the list, if not already present
function addIdentifierSignature(idSignature) {
if(!idSignatures.includes(idSignature)) {
idSignatures.push(idSignature);
}
}
// Remove the given identifier signature from the list, if present
function removeIdentifierSignature(idSignature) {
if(idSignatures.includes(idSignature)) {
let index = idSignatures.indexOf(idSignature);
idSignatures.splice(index, 1);
}
}
// Complete the input with the clicked identifier signature
function selectIdentifierSignature() {
selectedIdSignature = this.id;
idFilter.value = selectedIdSignature;
selectButton.removeAttribute('disabled');
}
// Update the ID dropdown options based on the input ID fragment
function updateIdDropdown() {
if(isSelected) {
return;
}
let updatedDropdownItems = document.createDocumentFragment();
let numberOfMatches = 0;
let idFragment = idFilter.value;
idSignatures.forEach(function(idSignature) {
if(idSignature.includes(idFragment)) {
let button = document.createElement('button');
button.setAttribute('class', 'dropdown-item');
button.setAttribute('type', 'button');
button.setAttribute('id', idSignature);
button.textContent = idSignature;
button.addEventListener('click', selectIdentifierSignature);
updatedDropdownItems.appendChild(button);
numberOfMatches++;
}
});
let header = document.createElement('h6');
header.setAttribute('class', 'dropdown-header');
header.textContent = 'Matching (' + numberOfMatches + ' of ' +
idSignatures.length + ')';
updatedDropdownItems.prepend(header);
idDropdown.innerHTML = '';
idDropdown.appendChild(updatedDropdownItems);
selectButton.textContent = 'Select';
}
// Reset the identifier
function resetId() {
isSelected = false;
idFilter.value = '';
idFilter.removeAttribute('readonly');
selectButton.textContent = 'Select';
selectButton.setAttribute('disabled', 'disabled');
raddecs = [];
raddecTableBody.innerHTML = '';
rssiTableBody.innerHTML = '';
}
// Update the selected transmitter data
function updateTransmitter(raddec) {
raddecs.unshift(raddec);
if(raddecs.length > RADDEC_HISTORY_LENGTH) {
raddecs.pop();
raddecTableBody.removeChild(raddecTableBody.lastChild);
}
insertRaddec(raddec);
updateRssiSignatures(raddecs);
}
// Update the receiver data
function updateReceiver(raddec) {
let receiverSignature = raddec.rssiSignature[0].receiverId +
SIGNATURE_SEPARATOR +
raddec.rssiSignature[0].receiverIdType;
if(currentReceiverSignature !== receiverSignature) {
currentReceiverSignature = receiverSignature;
cormorant.retrieveAssociations(baseUrl, receiverSignature, false,
function(associations) {
if(associations && associations.hasOwnProperty('url')) {
cormorant.retrieveStory(associations.url, function(story) {
rxStoryJson.textContent = JSON.stringify(story, null, 2);
cuttlefish.render(story, rxStoryVis);
});
}
});
}
}
// Insert a raddec into the DOM as a <tr>
function insertRaddec(raddec) {
let tr = document.createElement('tr');
tr.setAttribute('class', 'monospace animated-highlight-reelyactive');
appendTd(tr, new Date(raddec.timestamp).toLocaleTimeString(), 'text-center');
appendTd(tr, prepareEvents(raddec), 'text-center');
appendTd(tr, raddec.rssiSignature[0].receiverId, 'text-right');
appendTd(tr, raddec.rssiSignature[0].rssi, 'text-right');
appendTd(tr, prepareRecDecPac(raddec), 'text-center');
raddecTableBody.prepend(tr);
}
// Update rssiSignatures table
function updateRssiSignatures() {
let receiverHistories = [];
let updatedTable = document.createDocumentFragment();
raddecs.forEach(function(raddec, index) {
raddec.rssiSignature.forEach(function(element) {
if(index === 0) {
receiverHistories.push({ receiverId: element.receiverId,
rssiHistory: [ element.rssi ] });
}
else {
let isKnownReceiver = false;
receiverHistories.forEach(function(receiverHistory) {
if(receiverHistory.receiverId === element.receiverId) {
isKnownReceiver = true;
if(Array.isArray(receiverHistory.rssiHistory)) {
receiverHistory.rssiHistory.push(element.rssi);
}
else { console.log(element.receiverId, receiverHistory); }
}
});
if(!isKnownReceiver) {
let rssiHistory = Array(index).fill(null);
rssiHistory.push(element.rssi);
receiverHistories.push({ receiverId: element.receiverId,
rssiHistory: rssiHistory });
}
}
});
receiverHistories.forEach(function(receiverHistory) {
if(receiverHistory.rssiHistory.length < (index + 1)) {
receiverHistory.rssiHistory.push(null);
}
});
});
receiverHistories.forEach(function(receiverHistory) {
let tr = document.createElement('tr');
tr.setAttribute('class', 'monospace');
appendTd(tr, receiverHistory.receiverId, 'text-right');
receiverHistory.rssiHistory.forEach(function(rssi, index) {
if(index === 0) {
appendTd(tr, rssi, 'text-right animated-highlight-reelyactive');
}
else {
appendTd(tr, rssi, 'text-right');
}
});
updatedTable.append(tr);
});
rssiTableBody.innerHTML = '';
rssiTableBody.appendChild(updatedTable);
}
// Append a <td> with the given content to the given <tr>
function appendTd(tr, content, classNames) {
let td = document.createElement('td');
updateNode(td, content);
tr.appendChild(td);
if(classNames) {
td.setAttribute('class', classNames);
}
}
// Update the given node with the given content
function updateNode(node, content, append) {
append = append || false;
while(!append && node.firstChild) {
node.removeChild(node.firstChild);
}
if(content instanceof Element) {
node.appendChild(content);
}
else if(content instanceof Array) {
content.forEach(function(element) {
node.appendChild(element);
});
}
else {
node.textContent = content;
}
}
// Prepare the event icons
function prepareEvents(raddec) {
let elements = [];
raddec.events.forEach(function(event) {
let i = document.createElement('i');
let space = document.createTextNode(' ');
i.setAttribute('class', EVENT_ICONS[event]);
elements.push(i);
elements.push(space);
});
return elements;
}
// Prepare the receivers-decodings-packets string
function prepareRecDecPac(raddec) {
let maxNumberOfDecodings = 0;
raddec.rssiSignature.forEach(function(signature) {
if(signature.numberOfDecodings > maxNumberOfDecodings) {
maxNumberOfDecodings = signature.numberOfDecodings;
}
});
return raddec.rssiSignature.length + RDPS + maxNumberOfDecodings + RDPS +
raddec.packets.length;
}
// Fetch the device associations and story, update entry fields
function fetchAndUpdateStoryEntry() {
isSelected = true;
idFilter.setAttribute('readonly', 'readonly');
idDropdown.innerHTML = '';
cormorant.retrieveAssociations(baseUrl, selectedIdSignature, false,
function(associations) {
selectButton.setAttribute('disabled', 'disabled');
selectButton.textContent = 'Selected';
if(associations && associations.hasOwnProperty('url')) {
cormorant.retrieveStory(associations.url, function(story) {
txStoryJson.textContent = JSON.stringify(story, null, 2);
cuttlefish.render(story, txStoryVis);
});
}
});
}
// Event listeners
idFilter.addEventListener('keyup', updateIdDropdown);
resetButton.addEventListener('click', resetId);
selectButton.addEventListener('click', fetchAndUpdateStoryEntry);