Author Topic: gateway v9 httpendpoint api  (Read 434 times)

rd909

  • Newbie
  • *
  • Posts: 18
  • Country: us
gateway v9 httpendpoint api
« on: July 04, 2019, 02:59:54 PM »
I was able to modify the "httpEndPointHandler" function in gateway.js to accept commands, which could be used by some other app (homebridge in my case) to execute commands. Below is the code for the function that has my modifications in it. It's pretty hack-ish, but is a POC that hopefully someone else might find useful and expand upon.

Code: [Select]
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();
  }
}
« Last Edit: July 12, 2019, 10:49:44 AM by Felix »

Lukapple

  • Full Member
  • ***
  • Posts: 157
Re: gateway v9 httpendpoint api
« Reply #1 on: July 12, 2019, 10:19:34 AM »
Hi,
I'm already using it with HomeBridge, using custom api.js for reading data from database.

@Felix httpendpoint is probably used just for posting data to gateway or is there also a way to read current node data from DB file?

I need this to read a current temperature data of specific node from DB file. I'm currently using custom api.js script to achieve this, but it would be really great if Gateway officially supports this.
I think that extending httpendpoint funcionality with "read node data" shouldn't be problem, using:
db.findOne({_id:parseInt(id)}, function (err, doc) {...


Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 5992
  • Country: us
    • LowPowerLab
Re: gateway v9 httpendpoint api
« Reply #2 on: July 12, 2019, 03:39:16 PM »
Yes that would be a good addition, thanks.
I will consider doing it but I'm not sure when I get to it.
Maybe you could try it on your own given the pattern that is in place already.

Lukapple

  • Full Member
  • ***
  • Posts: 157
Re: gateway v9 httpendpoint api
« Reply #3 on: July 18, 2019, 01:53:26 AM »
Yes that would be a good addition, thanks.
I will consider doing it but I'm not sure when I get to it.
Maybe you could try it on your own given the pattern that is in place already.

I’ll do a branch on github. I think that the right way of doing it is that you change requests that add/update nodes to “POST” method and “GET” method should return existing data.

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 5992
  • Country: us
    • LowPowerLab
Re: gateway v9 httpendpoint api
« Reply #4 on: July 18, 2019, 09:05:09 AM »
Awesome that you are taking the task to contribute  ;)
You can post updates and progress here.
Thanks!