Skip to content

Commit

Permalink
sortable nodeList, binarySearch bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
LowPowerLab committed Feb 4, 2016
1 parent da0b30a commit 104cd1e
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 29 deletions.
45 changes: 42 additions & 3 deletions gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ serial.on('close', function serialCloseHandler(error) {
//sending message to front end via socket.io & setup timer to retry opening serial.
console.log(error.message);
process.exit(1);
})
});

serial.on("data", function(data) { processSerialData(data); });

Expand Down Expand Up @@ -166,11 +166,38 @@ io.sockets.on('connection', function (socket) {
socket.emit('EVENTSDEF', metricsDef.events);
socket.emit('SETTINGSDEF', settings);

//pull all nodes from the database
db.find({ _id : { $exists: true } }, function (err, entries) {
//console.log("New connection found docs: " + entries.length);
var orderCSV;
for (var i = entries.length-1; i>=0; i--)
if (!metricsDef.isNumeric(entries[i]._id)) //remove non-numeric id nodes
{
if (entries[i]._id == 'NODELISTORDER') //if node order entry was found, remember it
orderCSV = entries[i].order;
entries.splice(i,1);
}

//sort the list if nodes order entry was found, otherwise do a default ordering by label or by id when no label is set
entries.sort(orderCSV !== undefined ?
function (a, b) { return orderCSV.indexOf(a._id) - orderCSV.indexOf(b._id); }
:
function(a,b){ if (a.label && b.label) return a.label < b.label ? -1 : 1; if (a.label) return -1; if (b.label) return 1; return a._id > b._id; }
);

socket.emit('UPDATENODES', entries);
});

socket.on('UPDATENODELISTORDER', function (newOrder) {
db.findOne({_id:'NODELISTORDER'}, function (err, doc) {
var entry = {_id:'NODELISTORDER', order:newOrder};
if (doc == null)
db.insert(entry);
else
db.update({ _id: 'NODELISTORDER' }, { $set : entry});
io.sockets.emit('NODELISTREORDER', newOrder);
});
});

socket.on('UPDATENODESETTINGS', function (node) {
db.find({ _id : node._id }, function (err, entries) {
if (entries.length == 1)
Expand Down Expand Up @@ -495,7 +522,19 @@ scheduledEvents = []; //each entry should be defined like this: {nodeId, eventKe
function schedule(node, eventKey) {
var nextRunTimeout = metricsDef.events[eventKey].nextSchedule(node);
console.log('**** SCHEDULING EVENT - nodeId:' + node._id+' event:'+eventKey+' to run in ~' + (nextRunTimeout/3600000).toFixed(2) + 'hrs');

//clear any previous instances of the event
for(var s in scheduledEvents)
if (scheduledEvents[s].nodeId == node._id && scheduledEvents[s].eventKey == eventKey)
{
clearTimeout(scheduledEvents[s].timer);
scheduledEvents.splice(scheduledEvents.indexOf(scheduledEvents[s]), 1);
}

//schedule event in the future at calculated timer delay
var theTimer = setTimeout(runAndReschedule, nextRunTimeout, metricsDef.events[eventKey].scheduledExecute, node, eventKey); //http://www.w3schools.com/jsref/met_win_settimeout.asp

//remember the timer ID so we can clear it later
scheduledEvents.push({nodeId:node._id, eventKey:eventKey, timer:theTimer}); //save nodeId, eventKey and timer (needs to be removed if the event is disabled/removed from the UI)
}

Expand Down Expand Up @@ -524,4 +563,4 @@ db.find({ events : { $exists: true } }, function (err, entries) {
}
}
//console.log('*** Events Register db count: ' + count);
});
});
17 changes: 8 additions & 9 deletions logUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ exports.getLogName = function(nodeId, metricId) {

exports.getData = function(filename, start, end, dpcount) {
dpcount = dpcount || 600;
//console.log('getData: ' + filename + ',' + start + ','+end+','+dpcount);
if (dpcount>1500) dpcount = 1500;
if (dpcount<1) dpcount = 1;
if (dpcount<1 || start > end) return {};
Expand All @@ -45,17 +44,16 @@ exports.getData = function(filename, start, end, dpcount) {
for (var i=0; i<dpcount; i++)
{
pos = exports.binarySearch(fd,start+(i*interval),filesize);
//console.log(i +' pos: ' + pos);
last_time = timetmp;
fs.readSync(fd, buff, 0, 9, pos);
timetmp = buff.readUInt32BE(1);
value = buff.readInt32BE(5);

if ((timetmp!=last_time && timetmp>last_time) || last_time==0) {
var item = {t:timetmp*1000, v:value/10000};
//console.log('pos: ' + pos + ':' + JSON.stringify(item));
data.push(item);
}
if (pos == filesize-9) break;
}
fs.closeSync(fd);

Expand Down Expand Up @@ -98,7 +96,6 @@ exports.postData = function post(filename, timestamp, value) {
//timestamp is somewhere in the middle of the log, identify exact timestamp to update
fd = fs.openSync(filename, 'r');
pos = exports.binarySearchExact(fd,timestamp,logsize);
//console.log('pos found:' + pos);
fs.closeSync(fd);

if (pos!=-1)
Expand Down Expand Up @@ -126,12 +123,15 @@ exports.binarySearch = function(fileDescriptor,timestamp, filesize) {
var buff = new Buffer(4);
var time = 0;

// 30 here is our max number of itterations the position should usually be found within 20 iterations
fs.readSync(fileDescriptor, buff, 0, 4, end+1);
time = buff.readUInt32BE(0);
if (timestamp >= time) return end;

// 30 here is our max number of iterations, the position should usually be found within 20 iterations
for (i=0; i<30; i++)
{
//console.log('.');
// Get the value in the middle of our range
mid = start + Math.round((end-start)/16)*9;
mid = start + Math.round((end-start)/18)*9;
fs.readSync(fileDescriptor, buff, 0, 4, mid+1);
time = buff.readUInt32BE(0);
// If it is the value we want then exit
Expand All @@ -143,6 +143,7 @@ exports.binarySearch = function(fileDescriptor,timestamp, filesize) {
// If the time of the last middle of the range is more than our query time then next itteration is lower half less than our query time then nest ittereation is higher half
if (timestamp>time) start = mid; else end = mid;
}
return mid;
}

exports.binarySearchExact = function(fileDescriptor, timestamp, filesize) {
Expand All @@ -153,10 +154,8 @@ exports.binarySearchExact = function(fileDescriptor, timestamp, filesize) {
for (i=0; i<30; i++)
{
mid = start + Math.round((end-start)/18)*9;
//console.log('mid:' + mid);
fs.readSync(fileDescriptor, buff, 0, 4, mid+1);
tmp = buff.readUInt32BE(0);
//console.log('tmp:' + tmp);
if (tmp==timestamp) return mid;
if ((end-start)==9)
{
Expand Down
2 changes: 1 addition & 1 deletion metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ exports.events = {

switchMoteON_PM : { label:'SwitchMote ON at 5:30PM!', icon:'clock', descr:'Turn this switch ON every evening', nextSchedule:function(node) { return exports.timeoutOffset(17,30); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN1:1'}); } },
switchMoteOFF_AM : { label:'SwitchMote OFF at 7:30AM!', icon:'clock', descr:'Turn this switch OFF every morning', nextSchedule:function(node) { return exports.timeoutOffset(7,30); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN1:0'}); } },
switchMoteONBUZZ : { label:'SwitchMote ON Buzzer beep!', icon:'clock', descr:'Buzz gateway when switchmote is ON', serverExecute:function(node) { if (node.metrics['B1'] && node.metrics['B1'].value == 'ON' && (Date.now() - new Date(node.metrics['B1'].updated).getTime() < 2000)) { setTimeout(function() { sendMessageToGateway('BEEP'); }, 50); } }},
switchMoteONBUZZ : { label:'SwitchMote ON Buzzer beep!', icon:'clock', descr:'Buzz gateway when switchmote is ON', serverExecute:function(node) { if (node.metrics['B1'] && node.metrics['B1'].value == 'ON' && (Date.now() - new Date(node.metrics['B1'].updated).getTime() < 2000)) { setTimeout(function() { sendMessageToGateway('BEEP'); }, 5); } }},

//for the sprinkler events, rather than scheduling with offsets, its much easir we run them every day, and check the odd/even/weekend condition in the event itself
sprinklersOddDays : { label:'Odd days @ 6:30AM', icon:'clock', descr:'Run this sprinkler program on odd days at 6:30AM', nextSchedule:function(node) { return exports.timeoutOffset(6,30); }, scheduledExecute:function(node) { if ((new Date().getDate()%2)==1) sendMessageToNode({nodeId:node._id, action:'PRG 2:300 3:300 1:300 4:300 5:300' /*runs stations 1-5 (300sec each))*/}); } },
Expand Down
65 changes: 49 additions & 16 deletions www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@
<link type="text/css" rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<link type="text/css" rel="stylesheet" href="css/jqm-font-awesome-usvg-upng.min.css" />
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script>
<script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.time.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.selection.min.js"></script>
<script language="javascript" type="text/javascript" src="js/graphHelper.js"></script>
<script language="javascript" type="text/javascript" src="js/dateFormat.js"></script> <!-- for demo see http://jsfiddle.net/phZr7/1/ -->
<script language="javascript" type="text/javascript" src="https://cdn.rawgit.com/remy/bind.js/v1.0.1/dist/bind.min.js"></script>

<style type="text/css">
.ui-content { padding-top:0; }
.btn-text, #loadingsocket { font-size: 32px; }
Expand All @@ -79,6 +79,13 @@
font-size:16px;
}

h1, #nodeList li {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
cursor: default;
}

.center-div{ margin: 0 auto; }
.center-wrapper{ text-align: center; }
.center-wrapper * { margin: 0 auto; }
Expand Down Expand Up @@ -346,20 +353,18 @@ <h1>Terminal</h1>
<div data-role="header">
<a href="#homepage" class="ui-btn ui-corner-all ui-shadow ui-icon-home ui-btn-icon-left">Home</a>
<h1>Settings</h1>

<div class="ui-btn-right" data-role="controlgroup" data-type="horizontal" data-mini="true">
<a href="#logpage" data-role="button" data-icon="fa-terminal" data-iconpos="notext" title="terminal/log">Log</a>
<a href="#processExit" data-role="button" data-icon="power" data-iconpos="notext" data-transition="fade" title="Restart gateway app">Restart app</a>
</div>
</div>


<div data-role="main" class="ui-content">
<ul id="settingsList" data-role="listview" data-inset="true" data-theme="a" data-dividertheme="b"></ul>
<button id="settingsSave" class="ui-btn ui-btn-inline ui-btn-icon-left ui-icon-check">Save</button>
<button id="settingsCancel" class="ui-btn ui-btn-inline ui-btn-icon-left ui-icon-delete">Cancel</button>
</div>

<div data-role="footer">
<a href="#homepage" class="ui-btn ui-btn-right ui-corner-all ui-shadow ui-btn-icon-left ui-icon-delete" style="background-color:#FF9B9B;margin:.5em;">Cancel</a>
<a id="settingsSave" class="ui-btn ui-corner-all ui-shadow ui-btn-icon-left ui-icon-check" style="background-color:#9BFFBE;color:#000000;margin:.5em;">Save</a>
</div>
</div>

<script type="text/javascript">
Expand All @@ -380,6 +385,17 @@ <h1>Settings</h1>
var socket = io();
$('#nodeList').hide();

function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n); //http://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric/1830844#1830844
}

function sortNodeListByOrderCSV(newOrderCSV) {
var orderArray = newOrderCSV.split(',');
$('#nodeList').find('li:not(:first)').sort(function (a, b) {
return orderArray.indexOf($(a).attr('id')) - orderArray.indexOf($(b).attr('id'));
}).appendTo('#nodeList');
}

function LOG(data) {
$('#log').val(new Date().toLocaleTimeString() + ' : ' + data + '\n' + $('#log').val());
if ($('#log').val().length > 5000) $('#log').val($('#log').val().slice(0,5000));
Expand Down Expand Up @@ -409,7 +425,6 @@ <h1>Settings</h1>
$("#nodeList").empty();
$('#nodeList').append('<li id="uldivider" data-role="divider"><h2>Nodes</h2><span class="ui-li-count ui-li-count16">Count: 0</span></li>');
$('#nodeList').show();
entries.sort(function(a,b){ if (a.label && b.label) return a.label < b.label ? -1 : 1; if (a.label) return -1; if (b.label) return 1; return a._id > b._id; });
for (var i = 0; i < entries.length; ++i)
updateNode(entries[i]);
refreshNodeListUI();
Expand Down Expand Up @@ -471,11 +486,14 @@ <h1>Settings</h1>
boundSettings = Bind(settingsDef, settingsDefBindMap);
$("#settingsSave").click(function () {
socket.emit('UPDATESETTINGSDEF', boundSettings);
//$('#settingsList').empty();
$.mobile.navigate('#homepage', { transition : 'fade'});
});
});

socket.on('NODELISTREORDER', function(orderCSV) {
sortNodeListByOrderCSV(orderCSV);
});

$(document).on("pagecreate", "#eventAdd", function(){ if ($('addEventType').val()) $('#addEvent_OK').show(); else $('#addEvent_OK').hide(); });

socket.on('LOG', function(data) {
Expand Down Expand Up @@ -667,7 +685,7 @@ <h1>Settings</h1>

function updateNode(node) {
LOG(JSON.stringify(node));
if (node._id)
if (isNumeric(node._id))
{
nodes[node._id] = node;
var nodeValue = metricsValues(node.metrics);
Expand Down Expand Up @@ -697,6 +715,22 @@ <h1>Settings</h1>
showHiddenNodes = false;
$('#btnHiddenNodesToggle').css('background-color', '').hide();
}

//default jquery sortable - will only work in desktop browsers but is the best and most consistent (+ it's jquery native)
$('#nodeList').sortable({
items: 'li:not(:first)',
containment: 'parent',
opacity: 0.5,
delay: 200,
scroll: true,
update: function(event, ui) {
var listIds = [];
var items = $('#nodeList li:not(:first)');
for(var i=0;typeof(items[i])!='undefined';listIds.push(items[i++].getAttribute('id')));
socket.emit('UPDATENODELISTORDER', listIds.join(','));
},
});

$('#nodeList').listview('refresh'); //re-render the listview
}

Expand Down Expand Up @@ -998,10 +1032,10 @@ <h1>Settings</h1>
$('#addEventDescr').html(' ');
$('#addEvent_OK').hide();
$("#addEventType").empty();
$('#addEventType').append('<option value="">Select type...</option>');
for(var key in eventsDef)
if (!nodes[selectedNodeId].events || !nodes[selectedNodeId].events[key])
$('#addEventType').append('<option value="' + key + '">' + (eventsDef[key].label || key) + '</option>');
$('#addEventType').append('<option value="">Select type...</option>');
for(var key in eventsDef)
if (!nodes[selectedNodeId].events || !nodes[selectedNodeId].events[key])
$('#addEventType').append('<option value="' + key + '">' + (eventsDef[key].label || key) + '</option>');

$(document).on("pagebeforeshow", "#addEvent", function(event){
$("#addEventType").selectmenu('refresh');
Expand All @@ -1012,7 +1046,7 @@ <h1>Settings</h1>
$("#node_update").click("tap", function(event) {
notifyUpdateNode();
//updateNode(nodes[selectedNodeId]); //this will happen when node is sent back by server
$('#nodeList').listview('refresh');
refreshNodeListUI();
});

$("#addEvent_OK").click("tap", function(event) {
Expand All @@ -1027,7 +1061,6 @@ <h1>Settings</h1>
metric.pin = $('#btnPinMetric').hasClass('pinned') ? 1 : 0;
if (metric.graph!=undefined) metric.graph = $('#btnGraphMetric').hasClass('graphed') ? 1 : 0;
socket.emit('UPDATEMETRICSETTINGS', selectedNodeId, selectedMetricKey, metric);
//$('#nodeList').listview('refresh');
}
});

Expand Down

0 comments on commit 104cd1e

Please sign in to comment.