This is a good start. but how do I start interacting with the gateway app?
I did some reading on the architecture Felix put together and tried to reverse engineer the system.
There is a gateway.js app that runs on the Pi. This gateway app talks to motes via serial port and talks to client connections through a socket.io connection.
I took a look at the index.html file to understand the way the clients talk to the gateway over socket.io.
Actually with just some relatively simple editing (remove all of the visual aspects of the dashboard elements), I took the javascript directly from the client index.html file and used that as a base for my sms_gateway.js app.
When any client connects to the gateway, the gateway replies with a bunch of data about all of the nodes currently running on the gateway.
My first cut and getting the two to talk was, a basic test. On an incoming SMS, make a connection to the gateway and spit back a string-ified version of the node database along with all of their metrics.
This was a good second step. It shows incoming SMS messages are received and the sms_gateway can connect to the gateway and spit back results.
The code I lifted was basically pulling all of the socket.io event handlers from the index.html file and putting them into my sms_gateway.
var socketURL = 'http://192.168.1.234:8080'; // assumes this runs on the gateway Pi
var socket = require('socket.io-client')(socketURL); // Socket to the Pi Gateway
/*
* SOCKET EVENT HANDLERS, incoming socket messages from the PiGateway follow the following protocols:
* [list out the messages here...
*/
socket.on('connect', function () {
console.log('Socket connected!')
});
socket.on('connect_error', function (err) {
console.warn('Error connecting: ', err)
});
socket.on('connect_timeout', function () {
console.warn('Timeout trying to connect')
});
// These happen right after connections
socket.on('MOTESDEF', function(motesDefinition) {
motesDef = motesDefinition;
// $("#nodeMoteType").empty();
// $('#nodeMoteType').append('<option value="">Select type...</option>');
// for(var mote in motesDef)
// $('#nodeMoteType').append('<option value="' + mote + '">' + motesDef[mote].label || mote + '</option>');
// $("#nodeMoteType").selectmenu();
});
socket.on('METRICSDEF', function(metricsDefinition) {
metricsDef = metricsDefinition;
});
socket.on('EVENTSDEF', function(eventsDefinition) {
eventsDef = eventsDefinition;
// $('#addEventType').change(function() {
// if ($(this).val())
// {
// $('#addEventDescr').html('<span style="color:#000">Action: </span>' + (eventsDef[$(this).val()].icon ? '<span class="ui-btn-icon-notext ui-icon-'+eventsDef[$(this).val()].icon+'" style="position:relative;float:left"></span>' : '') + (eventsDef[$(this).val()].descr || key));
// $('#addEvent_OK').show();
// }
// else {
// $('#addEventDescr').html(' ');
// $('#addEvent_OK').hide();
// }
// });
});
socket.on('SETTINGSDEF', function(newSettingsDef) {
settingsDef = newSettingsDef;
// $('#settingsList').empty();
// settingsDefBindMap = {};
// for(var sectionName in settingsDef)
// {
// var sectionSettings = settingsDef[sectionName];
// if (!sectionSettings.exposed) continue;
// var sectionLI = $('<li data-role="list-divider">'+sectionName+'</li>');
// $('#settingsList').append(sectionLI);
//
// for(var settingName in sectionSettings)
// {
// var setting = sectionSettings[settingName];
// if (setting.exposed === false) continue;
// if (setting.value == undefined) continue;
// var settingLI;
// var type = 'type="'+(setting.type || 'text')+'"';
// if (setting.type == 'range') type+= ' min="'+setting.min+'" max="'+setting.max+'" step="0.01"';
// if (setting.type == 'checkbox') type+= (setting.value == 'true' ? ' checked' : ' unchecked') + ' ';
// if (setting.description)
// {
// settingLI = $('<li class="ui-field-contain">'
// +'<label>'
// +'<a href="#popupInfo-'+sectionName+'-'+settingName+'" data-rel="popup" data-transition="pop">'+settingName+'</a>'
// +'<div id="popupInfo-'+sectionName+'-'+settingName+'" data-role="popup" data-overlay-theme="b" class="popupInfo">'
// +'<b>'+settingName+'</b>: '+setting.description
// +'</div>'
// +':</label>'
// + (setting.type=='checkbox' ? '<label for="'+sectionName+'-'+settingName+'">'+setting.value+'</label>' : '')
// +'<input '+type+' name="'+sectionName+'-'+settingName+'" id="'+sectionName+'-'+settingName+'" value="'+setting.value+'"'+(setting.editable===false?' data-clear-btn="false" readonly="readonly"':' data-clear-btn="true"')+'></li>');
// }
// else
// {
// settingLI = $('<li class="ui-field-contain">'
// +'<label>'+settingName+':</label>'
// + (setting.type=='checkbox' ? '<label for="'+sectionName+'-'+settingName+'">'+setting.value+'</label>' : '')
// +'<input '+type+' name="'+sectionName+'-'+settingName+'" id="'+sectionName+'-'+settingName+'" value="'+setting.value+'"'+(setting.editable===false?' data-clear-btn="false" readonly="readonly"':' data-clear-btn="false"')+'></li>');
// }
// settingsDefBindMap[sectionName+'.'+settingName+'.value'] = '#'+sectionName+'-'+settingName;
// $('#settingsList').append(settingLI);
//
// //for range INPUTs, we need to rehidrate the value in the INPUT.value because jqueryUI does not do it (it injects an anchor where it keeps the value in an attribute, doh!)
// if (setting.type=='range' || setting.type == 'checkbox')
// {
// $(document).on("change", '#'+sectionName+'-'+settingName, { section:sectionName, setting: settingName }, function(e) {
// newVal = (this.type=='checkbox' ? this.checked.toString() : $(this).val());
// $('label[for*='+e.data.section+'-'+e.data.setting+']').html(newVal);
// boundSettings[e.data.section][e.data.setting].value = newVal;
// });
// }
// }
// }
// $('#settingsList').listview().listview('refresh').trigger("create");
// boundSettings = Bind(settingsDef, settingsDefBindMap);
});
socket.on('UPDATENODE', function(entry) {
updateNode(entry);
console.log('Received update:');
// refreshNodeListUI();
});
//*
//* This is a helper function for the socket callback that occurs when in inbound node update message is received from the gateway.
//*
function updateNode(node) {
console.log(JSON.stringify(node));
// for now, just spit it out to the Twilio sms phone number.
twilio.messages
.create({
body: JSON.stringify(node),
from: twilioNumber,
to: authorizedSubscribers[0]
})
.then(message => console.log("Sending to SMS: ", message.sid, " Status: ", message.status, "\n", JSON.stringify(node)))
.catch(e => { console.error('Got an error:', e.code, e.message); })
.done();
// everything below was primarily updating the web portal UI which I have mostly just commented out.
if (isNumeric(node._id))
{
nodes[node._id] = node;
var nodeValue = metricsValues(node.metrics);
var lowVoltageSetting = node.settings && node.settings['lowVoltageValue'] ? node.settings['lowVoltageValue'] : (motesDef[node.type] && motesDef[node.type].settings && motesDef[node.type].settings.lowVoltageValue ? motesDef[node.type].settings.lowVoltageValue : 0 /*boundSettings.general.lowVoltageValue.value*/);
var lowBat = node.metrics.V != null && node.metrics.V.value < lowVoltageSetting;
// var newLI = $('<li id="' + node._id + '"><a node-id="' + node._id + '" href="#nodedetails" class="nodedetails"><img class="productimg" src="images/'+getNodeIcon(node)+'"><h2>' + (nodeResolveString(node.label, node.metrics) || node._id) + ' ' + resolveRSSIImage(node) + ' ' + (lowBat ? '<img src="images/lowbattery.png" class="lowbattimg"/> ' : '') + ago(node.updated, 0).tag + (node.hidden ? ' <img class="listIcon20px" src="images/icon_hidden.png" />' : '') + '</h2><p>' + (nodeResolveString(node.descr || "", node.metrics) || ' ') + '</p>' + (nodeValue ? '<span class="ui-li-count ui-li-count16">' + nodeValue + '</span>' : '') + '</a></li>');
// var existingNode = $('#nodeList li#' + node._id);
// if (node.hidden)
// if (showHiddenNodes)
// newLI.addClass('hiddenNodeShow');
// else
// newLI.addClass('hiddenNode');
// if(existingNode.length)
// existingNode.replaceWith(newLI);
// else $('#nodeList').append(newLI);
// if (node._id == selectedNodeId) refreshNodeDetails(node);
// //update this node's events on the events page
// for(var key in node.events)
// {
// var evt = eventsDef[key];
// if (!evt) continue;
//
// var enabled = node.events[key].enabled;
// var execTime = node.events[key].executeDateTime;
// var dt = new Date(execTime);
// var execTimeString = execTime ? (' Due in <b>' + ago(dt.getTime(), 0).tag + '</b>') : '';
// var remainingExecTimeString = (dt.getDate() == new Date().getDate())
// ? execTime ? ' @ <b>' + dt.format('h:MMt') + '</b>' : ''
// : execTime ? ' on <b>' + dt.format('mmm-d @ h:MMt') + '</b>' : '';
// var newLI = $('<li id="'+node._id+'-'+key+'"><a data-icon="delete" node-id="' + node._id + '" event-id="' + key + '" href="#" class="eventEnableDisable"><span class="ui-btn-icon-notext ui-icon-'+ (enabled ? (evt.icon ? evt.icon : 'action') : 'minus') + (enabled ? " enabled" : " disabled") + '"></span><h2>[' + node._id + '] ' + evt.label + '</h2><p>Action: ' + (evt.descr || ' ') + '</p><p>' + execTimeString + remainingExecTimeString + '</p></a><a node-id="' + node._id + '" event-id="' + key + '" href="#" class="eventDelete" data-transition="pop" data-icon="delete"></a></li>');
//
// var allEventsListNode = $('#allEventsList li#' + node._id + '-' + key);
// if(allEventsListNode.length)
// allEventsListNode.replaceWith(newLI);
// else $('#allEventsList').append(newLI);
}
// $('#allEventsList').listview().listview('refresh');
}