Flic Home

    Community

    • Login
    • Search
    • Popular
    • Users
    1. Home
    2. jitmo
    3. Best
    • Profile
    • Following 0
    • Followers 0
    • Topics 3
    • Posts 10
    • Best 2
    • Controversial 0
    • Groups 0

    Best posts made by jitmo

    • RE: IRTCP v1.0.0 (Hub server for using IR module via HTTP)

      Hey @oskaremilsson and @Emil, great work on getting this together. I have built on top of this with the following:

      • timeout when recording an IR signal
      • optional logging of TCP requests/responses
      • ignore favicon.ico requests when using a browser
      • the server now has 4 methods using a semantic url path

      The four server methods are:

      • GET /putIRarray/<IR_NAME>:<IR_ARRAY> to store IR arrays crowd-sourced from others
      • GET /getIRarray/<IR_NAME> to share your IR arrays with others
      • GET /recordIR/<IR_NAME> to record an IR array, with timeout
      • GET /playIR/<IR_SEQUENCE> to play IR sequences

      <IR_NAME> (string) is the name you choose for your IR code(s)

      <IR_ARRAY> (string) is, without spaces, in the form from the Flic Hub SDK docs, eg: [<CARRIER_FREQ>,<ON_us>,<OFF_us>,<ON_us>,...]

      <IR_SEQUENCE> (string) is a comma-separated list of <IR_NAME>, eg: /playIR/tvOn or playIR/tvOn,lightsOff

      To stagger when the IR codes start use <IR_NAME>:<SECONDS_DELAY> (0.1 second precision), eg: playIR/tvOn:2.0,lightsOff

      Here's the code:

      // main.js
      
      const tcpServer = require('./tcpServer');
      
      // tcpServer.js
      
      // Based on work by Oskar Emilsson https://community.flic.io/topic/18043/irtcp-v1-0-0-hub-server-for-using-ir-module-via-http
      
      const net = require('net');
      
      const irUtils = require('./irUtils');
      
      // Set true to log TCP (HTTP) requests/responses
      const logRequestResponse = false;
      
      const respondToClient = function(c, statusCode, message, log) {
        
        var content = 'HTTP/1.1 '+statusCode+'\r\n'+'\r\n'+message;
      
        if(typeof log === 'undefined') log = logRequestResponse;
      
        if(log) {
          console.log('\n# HTTP RESPONSE');
          console.log(content);
          console.log('# END HTTP RESPONSE\n');
        }
      
        c.write(content);
        c.end();
      }
      
      var server = net.createServer(function(c) {
        console.log('Server connected');
      
        c.on('end', function() {
          console.log('Server disconnected\n');
        });
      
        c.on('data', function(data) {
      
          // Convert TCP content byte array to string
          var content = data.toString();
          
          // Ignore favicon requests from a browser
          if(content.indexOf('GET /favicon.ico') !== -1) {
            console.log('# ignoring favicon.ico request');
            return respondToClient(c, '200 OK', '', false);
          }
      
          if(logRequestResponse) {
            console.log('\n# HTTP REQUEST');
            console.log(content);
            console.log('# END HTTP REQUEST\n');      
          }
      
          // The first line of the raw TCP will look something like this "GET playIR/tvOn:2.0,lightsOff HTTP/1.1"
          // Check for URL paths /recordIR/<IR_NAME>, /playIR/<IR_SEQUENCE>, /putIRarray/<IR_NAME>:<IR_ARRAY> or /getIRarray/<IR_NAME>
      
          // <IR_ARRAY> is, without spaces, in the form from the docs [<CARRIER_FREQ>,<ON_us>,<OFF_us>,<ON_us>,...]
          
          // <IR_SEQUENCE> is a comma-separated list of <IR_NAME>, eg: playIR/tvOn or playIR/tvOn,lightsOff
          // To stagger when the IR codes start use <IR_NAME>:<SECONDS_DELAY> (0.1 second precision), eg: playIR/tvOn:2.0,lightsOff
      
          // From the Hub SDK documentation "If another play is started before a previous one has completed, it gets enqueued and starts as soon as the previous completes (max 100 enqueued signals)"
      
          var recordIRmatch = content.match(/GET \/recordIR\/(.[^ ]*) HTTP/);
          var playIRmatch = content.match(/GET \/playIR\/(.[^ ]*) HTTP/);
          var putIRarraymatch = content.match(/GET \/putIRarray\/(.[^ ]*) HTTP/);
          var getIRarraymatch = content.match(/GET \/getIRarray\/(.[^ ]*) HTTP/);
          
          if(recordIRmatch && recordIRmatch[1]) {
            // Start recording an IR signal
            irUtils.record(c, recordIRmatch[1]);
          }
          else if(playIRmatch && playIRmatch[1]) {
            // Play an IR signal or IR signal sequence
            var items = playIRmatch[1].split(',');
            irUtils.play(c, items);
          }
          else if(putIRarraymatch && putIRarraymatch[1]) {
            // Store an IR signal
            var splitPath = putIRarraymatch[1].split(':');
            if(splitPath.length == 2) {
              var irArray = JSON.parse(splitPath[1]);
              if(Array.isArray(irArray) && irArray.length % 2 === 0) {
                irUtils.put(c, splitPath[0], splitPath[1]);
              }
              else {
                respondToClient(c, '400 Bad Request', 'Use the form /putIRarray/<IR_NAME>:<IR_ARRAY>\r\n\r\n<IR_ARRAY> is, without spaces, in the form from the Flic Hub SDK docs [<CARRIER_FREQ>,<ON_us>,<OFF_us>,<ON_us>,...] and must have an even number of items (finishing with an <ON_us> item)');
              }
            }
            else {
              respondToClient(c, '400 Bad Request', 'Use the form /putIRarray/<IR_NAME>:<IR_ARRAY>\r\n\r\n<IR_ARRAY> is, without spaces, in the form from the Flic Hub SDK docs [<CARRIER_FREQ>,<ON_us>,<OFF_us>,<ON_us>,...] and must have an even number of items (finishing with an <ON_us> item)');
            }
          }
          else if(getIRarraymatch && getIRarraymatch[1]) {
            // Retrieve an IR signal
            irUtils.get(c, getIRarraymatch[1]);
          }
          else {
            respondToClient(c, '400 Bad Request', 'Valid url paths are recordIR/<IR_NAME> and playIR/<IR_SEQUENCE> \r\n\r\nWhere <IR_SEQUENCE> is a comma-separated list of <IR_NAME>, eg: playIR/tvOn or playIR/tvOn,lightsOff \r\n\r\nTo stagger when the IR codes start use <IR_NAME>:<SECONDS_DELAY> (0.1 second precision), eg: playIR/tvOn:2.0,lightsOff');
          }
      
        }); // on.data
      
      }); // net.createServer
      
      server.listen(1338, function() {
        console.log('Server bound', server.address().port);
      });
      
      exports.respondToClient = respondToClient;
      
      // irUtils.js
      
      const ir = require('ir');
      const datastore = require('datastore');
      
      const server = require('./tcpServer');
      
      // Set true to respond before playing IR signals (and after checking all signals have been recorded)
      // You may want a faster response for the system requesting the IR signal(s) playback, although you will not know if ir.play() fails
      const respondBeforePlaying = false;
      
      const _irSignal2str = function(signal) {
        // Instead of using signal.buffer use the JS object (undocumented, this might not always be available)
        // The object keys are "0", "1", etc and the values are the integers that need to be in the array
      
        // Convert the JS object to an array of integers
        var items = [];
        for(var i=0; i<Object.keys(signal).length;i++) {
          items.push(signal[i]);
        }
      
        return JSON.stringify(items);
      }
      
      const _str2irSignal = function(str) {
        return new Uint32Array(JSON.parse(str));
      }
      
      const put = function(c, name, data) {
        console.log('@ irUtils.put',name,data);
        
        datastore.put(name, data, function(err) {
          console.log('@ datastore.put callback');
          if (!err) {
            console.log('IR signal '+name+' stored');
            server.respondToClient(c, '200 OK', 'IR signal '+name+' stored');
          } else {
            console.error('# error: ', error);
            server.respondToClient(c, '500 Internal Server Error', 'Could not store IR signal');
          }
        }); // datastore.put
      }
      
      const get = function(c, name) {
        console.log('@ irUtils.get '+name);
      
        datastore.get(name, function(err, str) {
          console.log('@ datastore.get callback');
          if(!err && typeof str === 'string' && str.length > 0) {
            server.respondToClient(c, '200 OK', str);
          }
          else {
            server.respondToClient(c, '404 Not Found', 'Could not find IR signal '+name);
          }    
        }); // datastore.get
      }
      
      const record = function(c, name) {
        console.log('@ irUtils.record '+name);
        
        // Start recording
        ir.record();
      
        // Set up a timeout timer for 5 seconds
        var timeoutTimer = setTimeout(function(){
          ir.cancelRecord();
          console.log('Recording IR signal '+name+' TIMEOUT');
          clearTimeout(timeoutTimer);
          server.respondToClient(c, '408 Request Timeout', 'Recording IR signal '+name+' TIMEOUT');
          return;
        },5000);
      
        // Wait for recordComplete event
        ir.on('recordComplete', function(signal) {  
      
          console.log('@ ir.on.recordComplete');
          // Stop the timeout timer
          clearTimeout(timeoutTimer);
          
          // Convert the signal to a string
          var data = _irSignal2str(signal);
          console.log(data);
      
          // Store the data
          put(c, name, data);
      
        }); // ir.on.recordComplete
      }
      
      const play = function(c, items) {
        console.log('@ irUtils.play '+items);
      
        // Check all the IR codes exist
        const retrievalMs = 20;
        var index = 0;
        var irCodes = {};
        var errors = '';
      
        // datastore is async, so give each item time to be retrieved
        var fetchingTimer = setInterval(function(){
          var item = items[index].split(':')[0];
          if(typeof irCodes[item] !== 'undefined') {
            console.log('# '+item+' already retrieved');
            if(++index === items.length) clearTimeout(fetchingTimer);      
          }
          else {
            console.log('# getting '+item+' from datastore')
            datastore.get(item, function(err, str) {
              console.log('@ datastore.get callback');
              if(!err && typeof str === 'string' && str.length > 0) {
                irCodes[item] = str;
              }
              else {
                console.error('Cannot find IR code '+item+' in datastore.');
                errors += 'Cannot find IR code '+item+' in datastore. ';
              }    
              if(++index === items.length) clearTimeout(fetchingTimer);
            }); // datastore.get
          }
        },retrievalMs); // setInterval
      
        // Wait for datastore to finish
        setTimeout(function(){
      
          if(errors !== '') {
            server.respondToClient(c, '400 Bad Request', errors);
            return;
          }
      
          console.log(JSON.stringify(irCodes,null,2));
          
          if(respondBeforePlaying) server.respondToClient(c, '200 OK', 'Sending IR signal(s)');
          
          // Set up a timer to process the queue and pauses
          var pausingTenths = 0;
          var sendingTimer = setInterval(function(){
            if(pausingTenths > 0) {
              // Keep pausing
              pausingTenths--;
            }
            else {
              if(items.length > 0) {
                var itemSplit = items.shift().split(':');
                // Play the IR code
                console.log('# Sending IR code '+itemSplit[0]);
                var signal = _str2irSignal(irCodes[itemSplit[0]]);
                ir.play(signal, function(err) {
                  if(err) {
                    clearTimeout(sendingTimer);
                    if(!respondBeforePlaying) server.respondToClient(c, '500 Internal Server Error', 'Could not send IR signal '+itemSplit[0]);
                    return;
                  }
                });
      
                // Add a pause if requested
                if(itemSplit[1] && typeof parseFloat(itemSplit[1]) === 'number') {
                  var pause = parseFloat(itemSplit[1]);
                  console.log('# Adding '+pause+' seconds pause');
                  pausingTenths = parseInt(pause*10);
                }
              }
              else {
                // Finish up
                console.log('# Finished IR send');
                clearTimeout(sendingTimer);
                if(!respondBeforePlaying) server.respondToClient(c, '200 OK', 'Sent IR signal(s)');
              }
            }
      
          },100); // setInterval
          
        },retrievalMs*(items.length+1)); // setTimeout
      }
      
      exports.put = put;
      exports.get = get;
      exports.record = record;
      exports.play = play;
      
      posted in Flic Hub SDK
      jitmo
      jitmo
    • RE: Access integrations in SDK?

      Hey @rs, very late to the party here.

      The Flic hub does not appear to have third party integrations, although the Hue API looks pretty straightforward.

      Did you find the Hue developer docs? https://developers.meethue.com/develop/get-started-2/

      Did you have much luck/progress?

      How can I help?

      posted in Flic Hub SDK
      jitmo
      jitmo