function httpEndPointHandler(req, res) {
var queryString = url.parse(req.url, true).query; //parse query string
var ip = req.headers['x-forwarded-for'] /*|| req.connection.remoteAddress*/ ; //appended by nginx proxy
var id = queryString.id || ip;
//homebridge var
var cmd = queryString.cmd;
if (metricsDef.isValidNodeId(id)) {
if (metricsDef.isNumeric(id)) id = parseInt(id);
db.find({
_id: id
}, function(err, entries) {
var existingNode = {};
var matchedMetrics = 0;
if (entries.length == 1) existingNode = entries[0]; //update
existingNode._id = id;
if (metricsDef.isNumeric(id)) existingNode._ip = ip; //add/override IP address for HTTP requests, if node ID was specified as a number (so we know what IP to send requests back to)
existingNode.updated = Date.now(); //update timestamp we last heard from this node, regardless of any matches
if (existingNode.metrics == undefined) existingNode.metrics = {};
for (var queryStringKey in queryString) {
var matchingMetric;
var token;
for (var metric in metricsDef.metrics) //try to match a metric definition
{
token = queryStringKey.trim() + ':' + queryString[queryStringKey].trim();
if (metricsDef.metrics[metric].regexp.test(token)) {
var tokenMatch = metricsDef.metrics[metric].regexp.exec(queryStringKey + ':' + queryString[queryStringKey]);
matchingMetric = metricsDef.metrics[metric];
if (existingNode.metrics[matchingMetric.name] == null) existingNode.metrics[matchingMetric.name] = {};
existingNode.metrics[matchingMetric.name].label = existingNode.metrics[matchingMetric.name].label || matchingMetric.name;
existingNode.metrics[matchingMetric.name].descr = existingNode.metrics[matchingMetric.name].descr || matchingMetric.descr || undefined;
existingNode.metrics[matchingMetric.name].value = matchingMetric.value || metricsDef.determineValue(matchingMetric, tokenMatch);
existingNode.metrics[matchingMetric.name].unit = matchingMetric.unit || undefined;
existingNode.metrics[matchingMetric.name].updated = existingNode.updated;
existingNode.metrics[matchingMetric.name].pin = existingNode.metrics[matchingMetric.name].pin != undefined ? existingNode.metrics[matchingMetric.name].pin : matchingMetric.pin;
existingNode.metrics[matchingMetric.name].graph = existingNode.metrics[matchingMetric.name].graph != undefined ? existingNode.metrics[matchingMetric.name].graph : matchingMetric.graph;
//log data for graphing purposes, keep labels as short as possible since this log will grow indefinitely and is not compacted like the node database
if (existingNode.metrics[matchingMetric.name].graph == 1) {
var graphValue = metricsDef.isNumeric(matchingMetric.logValue) ? matchingMetric.logValue : metricsDef.determineGraphValue(matchingMetric, tokenMatch); //existingNode.metrics[matchingMetric.name].value;
if (metricsDef.isNumeric(graphValue)) {
var ts = Math.floor(Date.now() / 1000); //get timestamp in whole seconds
var logfile = path.join(__dirname, dbDir, dbLog.getLogName(id, matchingMetric.name));
try {
console.log('post: ' + logfile + '[' + ts + ',' + graphValue + ']');
dbLog.postData(logfile, ts, graphValue, matchingMetric.duplicateInterval || null);
} catch (err) {
console.error(' POST ERROR: ' + err.message); /*console.log(' POST ERROR STACK TRACE: ' + err.stack); */
} //because this is a callback concurrent calls to the same log, milliseconds apart, can cause a file handle concurrency exception
} else console.log(' METRIC NOT NUMERIC, logging skipped... (extracted value:' + graphValue + ')');
}
if (matchingMetric.name != 'RSSI') matchedMetrics++; //excluding RSSI because noise can produce a packet with a valid RSSI reading
break; //--> this stops matching as soon as 1 metric definition regex is matched on the data. You could keep trying to match more definitions and that would create multiple metrics from the same data token, but generally this is not desired behavior.
}
}
}
//prepare entry to save to DB, undefined values will not be saved, hence saving space
var entry = {
_id: id,
_ip: existingNode._ip,
updated: existingNode.updated,
type: existingNode.type || undefined,
label: existingNode.label || undefined,
descr: existingNode.descr || undefined,
hidden: existingNode.hidden || undefined,
rssi: existingNode.rssi,
metrics: Object.keys(existingNode.metrics).length > 0 ? existingNode.metrics : {},
events: existingNode.events,
settings: existingNode.settings,
multiGraphs: existingNode.multiGraphs,
icon: existingNode.icon || undefined
};
//console.log('HTTP REQUEST MATCH from: ' + id + ' : ' + JSON.stringify(entry));
//save to DB
if (cmd != 'sts') //don't write to the log or update the db for homebridge status update requests
{
db.findOne({
_id: id
}, function(err, doc) {
if (doc == null) {
if (settings.general.genNodeIfNoMatch.value == true || settings.general.genNodeIfNoMatch.value == 'true' || matchedMetrics) {
db.insert(entry);
console.log(' [' + id + '] DB-Insert new _id:' + id);
} else return;
} else
db.update({
_id: id
}, {
$set: entry
}, {}, function(err, numReplaced) {
console.log(' [' + id + '] DB-Updates:' + numReplaced);
});
//publish updated node to clients
io.sockets.emit('UPDATENODE', entry);
//handle any server side events (email, sms, custom actions)
handleNodeEvents(entry);
});
}
res.writeHead(200, {
'Content-Type': 'application/json'
});
if (cmd == 'sts' || cmd == 'lck' || cmd == 'ulk' || cmd == 'cls' || cmd == 'opn') { //custom returns for homebridge commands
if (cmd == 'sts') { //this is a garagedoor status check
res.write(JSON.stringify({
status: entry.metrics.Status.value,
LockStatus: entry.metrics.LockStatus.value
}));
} else { //actually do something
sendMessageToNode({
nodeId: id,
action: cmd
})
res.write(JSON.stringify({
status: entry.metrics.Status.value,
LockStatus: entry.metrics.LockStatus.value
}));
}
} else
res.write(JSON.stringify({
status: 'success',
message: 'SUCCESS!',
matchedMetrics: matchedMetrics
}));
res.end();
});
} else {
res.writeHead(406, {
'Content-Type': 'application/json'
});
res.write(JSON.stringify({
status: 'error',
message: 'FAIL, invalid id:' + id
}));
res.end();
}
}