• part 4 of 5 of: GPS Module, Extensions, and u-blox NEO-6M GPS receiver

    @Gordon, had some time to think... and code... under the influence of your input.

    The code below is a first attempt.

    // GPS2.js
    
    // ---- beg (modified) GPS module code
    
    function ggaHandler(line, callback) {
      var tag = line.substr(3,3);
      if (tag=="GGA") {
        var d = line.split(",");
        var dlat = d[2].indexOf(".");
        var dlon = d[4].indexOf(".");
        callback({ tag: tag,
          time : d[1].substr(0,2)+":"+d[1].substr(2,2)+":"+d[1].substr(4,2),
          lat : (parseInt(d[2].substr(0,dlat-2),10)+parseFloat(d[2].substr(dlat-2))/60)*(d[3]=="S"?-1:1),
          lon : (parseInt(d[4].substr(0,dlon-2),10)+parseFloat(d[4].substr(dlon-2))/60)*(d[5]=="W"?-1:1),
          fix : parseInt(d[6],10),
          satellites : parseInt(d[7],10),
          altitude : parseFloat(d[9])
        });
        return true;
      } else {
        return false;
      }
    }
    
    function handle(line,handlers) {
      var idx = handlers.length; while(idx > 0) { 
        idx--; 
        try {
          if (handlers[idx][0](line,handlers[idx][1])) return;
        } catch(x) {
          if (console) console.log("GPS:",x);
        }
      }
    }
    
    var connect = function(serial, handling) { // replace with next line for module
    // exports.connect = function(serial, handling) {
      var handlers = (typeof handling == "function")
            ? [[null,handling]] : handling;
      if (!handlers[0][0]) handlers[0][0] = ggaHandler; 
      var gps = { line:"", handle:handle, handlers: handlers, enabled: true };
      serial.on('data', function(data) {
        gps.line += data;
        var idx = gps.line.indexOf("\n");
        while (idx>=0) {
          var line = gps.line.substr(0, idx);
          gps.line = gps.line.substr(idx+1);
          if (gps.enabled) gps.handle(line, gps.handlers);
          idx = gps.line.indexOf("\n");
        }
        if (gps.line.length > 80)
          gps.line = gps.line.substr(-80);
      });
      return gps;
    }
    ; var gps = {connect: connect}; // drop line for module
    
    
    // ---- end (modified) GPS module code
    
    
    // common setup
    console.log("Serial4 > ",Serial4);
    console.log("Serial4.setup() > ", Serial4.setup(9600,{tx:C10,rx:C11}));
    
    // var gps = require("GPS"); // uncomment when using GPS code as module
    console.log("gps = req(GPS) >",gps);
    
    // (default) callback for gps
    var callback = function(data) { 
      console.log("data > ",data);
    };
    
    function go() { // connects the gps and gets it handling the lines
      console.log("gps.connect() >", gps = gps.connect(Serial4,callback));
    }
    
    function pause() { // pauses the handling of lines
      gps.enabled = false;
    }
    
    function resume() { // resumes the handling of lines
      gps.enabled = true;
    }
    

    Output in console:

    "time": "09:22:57",```

    data > {```

    "lat": 41.22417449999, "lon": -102.87471283333, "fix": 2, "satellites": 8, "altitude": 74.8 }```

    Basically - for the usage of the module - either:

    • a callback function accepting a the data object of the module's line handler is passed

    or:

    • an array of tuples of lineHandlers and callbacks are passed.

    Basically - for the implementation -

    1. Paired / tuples - or chain - of line handlers and callbacks stored as handlers are used (implemented as array of 2-elements-arrays, with lineHandler as first and callback as second element of the 2-elements-arrays). A lineHandler looks at the line and calls the paired up callback with the handled line data. ggaHandler() - current module's handleGPSLine - is an example of a lineHandler. callback is an example of a callback and does the 'stuff' the GPS module user wants to have done for the line by the (paired up) lineHandler.
    2. For each line from the GPS receiver, the module iterates over this array of handlers in handle() function and stop looping when handler did 'hit'. The handlers, such as ggaHandler() handling GGA tagged lines, return true when handling the line, otherwise return false (line 38).
    3. The same data structure and algorithms are used for both 100% compatible basic and extended mode: if first tuple's lineHandler is null (evaluates to false), the modules basic ggaHandler() is reused for it (line 40).
    4. Like line, handle() and handlers[] - and an extra enabled flag - are kept in the object returned by the connect() function

    Btw, I would consider above approach a Lambda or functional approach, where after module load 'no' application nor object oriented object is (yet) available, but merely a bootstrap function, which is not even equally to a 'new' or a constructor. This 'into an object wrapped' function has first to be called in order to get a (useful) object (with state, and now also with function) back. - Btw, what is/was the rational of returning the {line:""} object on .connect()? - The module documentation http://www.espruino.com/Writing+Modules follows more the object-oriented approach, even though - due to the nature of JS - the 'class definition' is a function. For the GPS module I would like to have a similar approach chosen, even if the GPS stays a Singleton / would not have a constructor, so that already with require("GPS") an object is available for custom alteration. I will look into this option in a next phase while - of course - keeping 100% compatibility with current 'usage documentation'.

    I still try to find what's right for Espruino. Having now lived many years - and still live - in a world of memory abundance, doing the right thing for Espruino is a welcome challenge. Not that I waste memory or cycles - I actually have the opposite habit - because I started out with having just 16KB (Kilo bytes) for application AND data AND ANY utility code - such as ISAM-create/read/update/delete, input routines, etc., and some of my Espruino-like hardware real world projects had just 256 bytes and 1KB EPROM and 4MHz 8-bit processing capabilities. Since then there is always the frugal guy in the back of my head watching over my spending of both memory and cycles, because even in today's memory and cycle paradise resources are limited... if not technically, then more so economically (currently building software for 15K+ concurrent transactional users - where user AND operator has to be kept happy: first one w/ snappy response times and latter one w/ spending 'no fancy' $s).

    @Gordon, some pointers about the implementation of js in Espruino is very welcome here. I provide you an example in more detail at (see http://forum.espruino.com/conversations/255954) from code I'm working on: An array with a lot of small string elements in source code uses obviously more memory than one with few but long strings that later are chopped up into the small ones and stored again in the very same array. The short string are of the form of "keyNMO".

    Back to the GPS discussion at hand:

    Add the following code to the above one, send code to board, and use command go2() in the command pane to get the gps connected and handling lines (just) with the custom RMC line handler instead of the module's own ggaHandler. For simplicity, same already known callback is used.

    var handlers =
    [ [ function(line,callback) { // rmcHandler
          var tag = line.substr(3,3);
          if (tag=="RMC") {
            var d = line.split(",");
            callback(
              { timc: d[1].substr(0,6)
              , latc: (parseInt(d[3].substr(0,2),10) + parseFloat(d[3].substr(2))/60) * (d[4]=="S"?-1:1)
              , lonc: (parseInt(d[5].substr(0,3),10) + parseFloat(d[5].substr(3))/60) * (d[4]=="W"?-1:1)
              , velc: parseFloat(d[7])
              , angl: d[8]
              , datc: d[9]
              });
            return true;
          } else {
            return false;
          }
         }
      , callback
      ]
    ];
    
    function go2() { // connects the gps and gets it handling the lines
      console.log("gps.connect() >", gps = gps.connect(Serial4,handlers));
    }
    
    

    Output in console:

    "timc": "103314",```

    "angl": "",```

    }```

    "timc": "103315",```

    "angl": "",```

    }```

    You may notice that the ggaHandler 'is lost' for good... (became inaccessible for good).

    The module's valuable ggaHandler can though easily be reused/included in the custom handling. Just provide the first tuple of 'handler duos' with null (no) line handler. On connect, the module's ggaHandler will be pulled and paired up with the callback provided in the first handler duo. To make that obvious / visible, a different calback2 call back is provided.

    Add the following code to the above one, send code to board, and use command go2() in the command pane to get the gps connected and handling now two lines, the GGA tagged line with the module's own line handler and callback2(), and the RMC tagged line with the custom line handler and initially established callback().

    // special callback for in reuse of GPS's module own ggaHandler
    var callback2 = function(data) { 
      console.log("cbk2 > ",data);
    };
    
    var handlers2 =
    [ [ null // re-using GPS module's ggaHandler...
      , callback2 // ...w/ callback2
      ]
    , [ function(line,callback) { // rmcHandler
          var tag = line.substr(3,3);
          if (tag=="RMC") {
            var d = line.split(",");
            callback(
              { timc: d[1].substr(0,6)
              , latc: (parseInt(d[3].substr(0,2),10) + parseFloat(d[3].substr(2))/60) * (d[4]=="S"?-1:1)
              , lonc: (parseInt(d[5].substr(0,3),10) + parseFloat(d[5].substr(3))/60) * (d[4]=="W"?-1:1)
              , velc: parseFloat(d[7])
              , angl: d[8]
              , datc: d[9]
              });
            return true;
          } else {
            return false;
          }
         }
      , callback
      ]
    ];
    
    function gox() { // connects the gps and gets it handling the lines
      console.log("gps.connect() >", gps = gps.connect(Serial4,handlers2));
    }
    

    Output in console:

    "timc": "124700",```

    "angl": "",```

    }```

    "time": "12:47:01", "lat": 41.2242155, "lon": -102.87470616666, "fix": 2, "satellites": 9, "altitude": 73.8 }```

    I'm not really sure about the overall value of enabled - but I liked the option to to shut up the console... - last but not least for copying console output to this post without having a nervously scrolling away text - or, in the target application - to quiet down the display... ;)

    What should be discussed is the value of the try-catch-block. In different context I had issues with the process stopping after some time - less than one hour - and errors showing in the console. Especially with line handlers that do not take into account when the GPS is not yet providing complete data, for example, while still trying to get enough satellites for decently reliable position determination.

    What I'm though confident about is:

    1. 100% compatibility
    2. Easy, simple, but quite powerful and flexible extensibility
    3. Handling can be modified even after being established
    4. Extensibility of the extension showed in the example, for example, adding the a 3rd array element to name the tuples for addressable change or removal - preferably when disabled
    5. ...and many things more.

    part 4 of 5 of: GPS Module, Extensions, and u-blox NEO-6M GPS receiver

About

Avatar for allObjects @allObjects started