• In this update, the project uses the GPS module variation with the array of tuples of lineHandlers and callbacks - as discussed in http://forum.espruino.com/conversations/­255757 - GPS Module, Extensions, and u-blox NEO-6M GPS receiver.

    This is the running code - with a first cut of the display implementation. Since the display poses significant performance challenges, expect updates, and furthermore, expect updates too for the GPS module for handling multiple GPS sentences, for which the implementation has not settled yet. As usual I (will) add shot and clip to see what's going on.

    Don't try to copy from below code section into Web IDE: it will corrupt the code. Download the code from uploaded navi5p.js file at the very bottom of this post.

    // navi5p.js
    
    // ---- beg (modified) GPS module code
    
    function ggaHandler(line, callback) { // ggaHandler
      var tag = line.substr(3,3);
      if (tag=="GGA") {
        var d = line.split(",");
        var dlat = d[2].indexOf(".");
        var dlon = d[4].indexOf(".");
        callback({
          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)+pars­eFloat(d[2].substr(dlat-2))/60)*(d[3]=="­S"?-1:1),
          lon : (parseInt(d[4].substr(0,dlon-2),10)+pars­eFloat(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 (line.indexOf("$GPGGA") > -1) { console.log("---"); }
    // console.log(line);
          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:"", 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) 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
    
    // ===== NAVI ===== to display (changed) data on (ILI9341 controlled) LDC as 'sent' by GPS
    
    var navi = 
    { lcd: null
    , gps: null
    
     , data:
      { timAdj: -7
      , timc: "#"
      , fixa: -1
      , sata: -1
      , latcn: -999.9
      , latcs: ["#","$"]
      , loncn: -999.9
      , loncs: ["#","$"]
      , velcs: "####"
      }
     
    , input:
     {  cnt: 0, cnt2: 0
      , tag: function(n,d,p,v,v0) { 
        }
      , timc: function(n,d,p,v,v0) {
          this.cnt = (this.cnt < 9) ? this.cnt + 1 : 0;
          if ((this.cnt2 = (this.cnt2 < 59) ? this.cnt2 + 1 : 0) == 10) v0 = "#";
          var l,s,x,y;
          if (v && v.length == 6) { 
          var j = -1, i = -1; while (i < 5) { i++;
            if (v.charAt(i) != v0.charAt(i)) { j = i; i = 6; } }
          if (j > -1) {
            d.timc = v;
            v0 = v.split("");
            if (j < 2) { j = 0;
              i = parseInt(v[0],10) * 10 + parseInt(v[1],10) + d.timAdj;
              i = (i < 0) ? i + 24 : (i > 24) ? i - 24 : i;
              v0[1] = i % 10; v0[0] = (i - v0[1]) / 10;
            }
            j = (j >= 4 ) ? j + 2 : (j >= 2) ? j + 1 : j;
            v0.splice(4,0,":"); v0.splice(2,0,":"); 
    //        console.log(".......".substr(0,j) + v0.join("").substr(j));
            s = n.fonts[2], x = 240 - 0 - 8 * s * 0.7, y = 5 + 0 * s * 1.6;
            n.setClr(0); n.lcd.fillRect((i=x+j*s*0.7),y,i+(8-j)*s­*0.7,y+s*1.5);
            while (j < 8) { n.drawString(2,6,v0[j],x + j * s * 0.7,y); j++; }
          } } else {
            s = n.fonts[2], x = 240 - 0 - 8 * s * 0.7, y = 5 + 0 * s * 1.6;
            n.setClr((this.cnt % 2) * 2); n.lcd.fillRect((l=x),y,l+(1)*s*0.7,y+s*1­.5);
          }
        }
      , fixa: function(n,d,p,v,v0) {
          if (this.cnt2 == 20) { v0 = -1; } else { if (this.cnt != 2) return; }
          if (v != v0) {
            d.fixa = v;
            var s = n.fonts[2], x = 5, y = 5 + 1 * s *1.6;
            n.setClr(0); n.lcd.fillRect((i=x),y,i+6*s*0.8,y+s*1.5­);
            n.drawString(2,((v) ? 3 : 2),((v) ? "OK " + v  : "NOT ok"),x,y);
          }
        }
      , sata: function(n,d,p,v,v0) {
          if (this.cnt2 == 20) { v0 = -1; } else { if (this.cnt != 4) return; }
          if (v != v0) {
            d.sata = v;
            var s = n.fonts[2], x = 5 + 7 * s * 0.7, y = 5 + 1 * s * 1.6;
            n.setClr(0); n.lcd.fillRect((i=x),y,i+3*s*0.7,y+s*1.5­);
            n.drawString(2,4,v + "s",x,y);
          }
        }
      , latcs: function(n,d,p,v,v0) { 
          if (this.cnt2 == 40) { v0 = ["#","X"]; } else { if (this.cnt != 6) return; }
          var v_0 = v[0], v0_0 = v0[0]; if (v_0 && (v_0.length == 9)) {
          var j = -1, i = -1; while (i < 8) { i = (i == 4) ? i + 2 : i + 1;
            if (v_0.charAt(i) != v0_0.charAt(i)) { j = i; i = 9; } }
          var l,s,x,y;
          if (j > -1) {
            d.latcs[0] = v_0; d.latcn = 9999.9;
            v_0 = v_0.split("");
            j = (j >= 2) ? j + 1 : j; 
            v_0.splice(2,0,"°"); v_0[5] = "'";
    //        console.log(".......".substr(0,j) + v_0.join("").substr(j));
            l = n.fonts[2];
            s = n.fonts[1], x = 240 - 5 - 12 * s * 0.7, y = 5 + ((l * 2) + (s * 0)) * 1.6;
            n.setClr(0); n.lcd.fillRect((i=x+j*s*0.7),y,i+(10-j)*­s*0.7,y+s*1.5);
            while (j < 10) { n.drawString(1,1,v_0[j],x + j * s * 0.7,y); j++; }
          }
          if (v[1] != v0[1]) { if (v[1] && (v[1].length == 1)) {
            d.latcs[1] = v[1]; d.latcn = 9999.99;
            l = n.fonts[2];
            s = n.fonts[1], x = 240 - 1 * s * 1.1, y = 5 + l * 2 * 1.6;
            n.setClr(0); n.lcd.fillRect((i=x),y,i+1*s*0.7,y+s*1.5­);
            n.drawString(1,1,v[1],x,y);
          } } }
        }
      , loncs: function(n,d,p,v,v0) {
          if (this.cnt2 == 50) { v0 = ["#","X"]; } else { if (this.cnt != 8) return; }
          var v_0 = v[0], v0_0 = v0[0]; if (v_0 && (v_0.length == 10)) {
          var j = -1, i = -1; while (i < 9) { i = (i == 4) ? i + 2 : i + 1;
            if (v_0.charAt(i) != v0_0.charAt(i)) { j = i; i = 10; } }
          var l,s,x,y;
          if (j > -1) {
            d.loncs[0] = v_0; d.loncn = 9999.9;
            v_0 = v_0.split("");
            j = (j >= 3) ? j + 1 : j; 
            v_0.splice(3,0,"°"); v_0[6] = "'";
    //        console.log(".......".substr(0,j) + v_0.join("").substr(j));
            l = n.fonts[2];
            s = n.fonts[1], x = 240 - 5 - 13 * s * 0.7, y = 5 + ((l * 2) + (s * 1)) * 1.6;
            n.setClr(0); n.lcd.fillRect((i=x+j*s*0.7),y,i+(11-j)*­s*0.7,y+s*1.5);
            while (j < 11) { n.drawString(1,1,v_0[j],x + j * s * 0.7,y); j++; }
          }
          if (v[1] != v0[1]) { if (v[1] && (v[1].length == 1)) {
            d.loncs[1] = v[1]; d.loncn = 9999.99;
            l = n.fonts[2];
            s = n.fonts[1], x = 240 - 1 * s * 1.3, y = 5 + ((l * 2) + (s * 1)) * 1.6;
            n.setClr(0); n.lcd.fillRect((i=x),y,i+1*s*0.7,y+s*1.5­);
            n.drawString(1,1,v[1],x,y);
          } } }
        }
      , velcs: function(n,d,p,v,v0) {
          if (this.cnt2 == 30) { v0 = "#"; } else { if (this.cnt % 3 != 0) return; }
          if (v && (v.length == 5)) {
          var j = -1, i = -1; while (i < 4) { i++;
            if (v.charAt(i) != v0.charAt(i)) { j = i; i = 4; } }
          var l,s,x,y;
          if (j > -1) {
            d.velcs = v;
            v = v.split("");
    //        console.log(".......".substr(0,j) + v.join("").substr(j));
            l = n.fonts[2];
            s = n.fonts[1], x = 240 - 5 - 5 * s * 0.7, y = 5 + ((l * 2) + (s * 2)) * 1.6;
            n.setClr(0); n.lcd.fillRect((i=x+j*s*0.7),y,i+(5-j)*s­*0.7,y+s*1.5);
            while (j < 5) { n.drawString(1,1,v[j],x + j * s * 0.7,y); j++; }
          } }
        }
      }
      
    , initDsp: function() {
        this.lcd.clear();
      }
    , clrs:
        [ [ 0.0,  0.0,  0.0 ] // 0 = black
        , [ 1.0,  1.0,  1.0 ] // 1 = white
        , [ 1.0,  0.8,  0.8 ] // 2 = light red
        , [ 0.8,  1.0,  0.8 ] // 3 = light green
        , [ 0.9,  0.9,  1.0 ] // 4 = light blue
        , [ 1.0,  1.0,  0.0 ] // 5 = yellow
        , [ 1.0,  1.0,  0.9 ] // 6 = light yellow
        ]
    , cid: -1
    , setClr: function(cid) { 
        if ((cid >= 0) && (this.cid != cid)) { this.cid = cid;
           var rgb = this.clrs[cid]; lcd.setColor(rgb[0], rgb[1], rgb[2]);
      } }
    , fonts:
        [ 20 // 0 = 'small'
        , 24 // 1 = 'medium' 
        , 32 // 2 = 'large'
      ]
    , fid: -1
    , setFont: function(fid) {
        if ((fid >= 0) && (this.fid != fid)) { this.fid = fid;
          lcd.setFontVector(this.fonts[fid]);
      } }
    , drawString: function(f,c,s,x,y) {
        this.setFont(f); this.setClr(c); this.lcd.drawString(s,x,y);
      }
    , handleGGA: function(data) {
      }
    , init: function(lcd,gps) {
        this.lcd = lcd;
        this.gps = gps;
        this.initDsp();
        this._connectGPSHandlers(gps.handlers);
        gps.enabled = true;
      }
    , _connectGPSHandlers: function(handlers) {
        var _this = this, d = this.data; 
        handlers[0][1] = handlers[1][1] = function(data) {
          for (var p in data) { _this.input[p](_this,d,p,data[p],d[p]); } };
      }
    };
    
    // ===== GPS =====
    
    // ----- gpsSetup
    Serial4.setup(9600,{tx:C10,rx:C11});
    // var gps = require("GPS"); // uncomment when using GPS code as module
    
    // ----- gpsHandlers - call navi to handle the messages
    
    var gpsHandlers =
    [ [ function(line,callback) { // ggaHandler ('clone' of GPS module's)
          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 : "GGA"
     //       , tima : d[1].substr(0,2)+":"+d[1].substr(2,2)+":­"+d[1].substr(4,2)
     //       , lata : (parseInt(d[2].substr(0,dlat-2),10)+pars­eFloat(d[2].substr(dlat-2))/60)*(d[3]=="­S"?-1:1)
     //       , lona : (parseInt(d[4].substr(0,dlon-2),10)+pars­eFloat(d[4].substr(dlon-2))/60)*(d[5]=="­W"?-1:1)
              , fixa : parseInt(d[6],10)
              , sata : parseInt(d[7],10)
     //       , alta : parseFloat(d[9])
              });
            return true; } else { return false; }
          } 
      , function(data) { }
      ]
    , [ function(line,callback) { // rmcHandler
          var tag = line.substr(3,3);
          if (tag=="RMC") {
            var d = line.split(",");
            callback(
              { tag:  "RMC"
              , timc:  d[1].substr(0,6)
              , latcs: [d[3].substr(0, 9),d[4]]
    //        , latcn: (parseInt(d[3].substr(0,2),10) + parseFloat(d[3].substr(2))/60) * (d[4]=="S"?-1:1)
              , loncs: [d[5].substr(0,10),d[6]]
    //        , loncn: (parseInt(d[5].substr(0,3),10) + parseFloat(d[5].substr(3))/60) * (d[6]=="W"?-1:1)
              , velcs: d[7].substr(0,5)
    //        , velc:  parseFloat(d[7])
    //        , angc:  d[8]
    //        , datc:  d[9]
              });
            return true; } else { return false; }
         }
       , function(data) { }
      ]
    ];
    
    // ===== LCD ==== 
    
    // ---- (ILI9341) common setup;
    B2.set();
    SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000});
    var lcd = require("ILI9341");
    
    
    // function naviInit() {
      lcd = lcd.connect(SPI1, B6, B8, B7, function(){
        setTimeout(function(){
          gps = gps.connect(Serial4, gpsHandlers);
          navi.init(lcd,gps);
        },100);
      });
    // }
    

    The first shot was taken at the moment when the last digit of the time in top row was re-drawn - digit 8. The clip shows the sequence after 'warm'-reboot (send code to board, GPS already locked in). You will notice the partial drawing... because of the display's performance drawing strings... You can see code of the sort of this.cnt = ... this.cnt + ... and this.cnt2 = ...this.cnt2 + .... These are counters from 0..9 and 0..59 - updated every second - that control partial and staggered full re-draw of the data in addition to the code that triggers partial redraw only on data changes. The redrawing code is still in its infancy and needs to be refactored for less redundancy (copy-paste-modify is good only for figuring something out). The second shot was taken after returning from work with taking the GPS for a spin for the commute.

    The display shows - from top to bottom:

    1. Time - adjusted to time zone from GPS receiver's UTC in very light yellow color (row 1)
    2. OK and Fix value of NMEA's $GGA sentence in light green color - value 1..7 are 'good' values. For the value 0 Not ok is shown in light red color (left half of row 2) - and number of satellites discovered by the GPS receiver, like 8s in light blue color (right half of row 2)
    3. Latitude in degrees, minutes, and decimal fractions of minutes (seconds and fractions 'spared' for later) and N/S hemisphere, all in white (row 3)
    4. Longitude in degrees, minutes, and decimal fractions of minutes (seconds and fractions 'spared' for later) and E/W of Prime Meridian (Greenwich, London - see http://en.wikipedia.org/wiki/Primemeridian(Greenwich)), all in white (row 4).
    5. Velocity over ground in nautical miles (55 Nautical Miles ~~ 65 Statuary US Miles), all in white; shows fanny values when not moving at all. (row 5).

    Note: the degree symbol - character - is not part of LCD controller's font - assuming the LCD controller takes the character and draws it... (@Gordon, is that assumption correct?)

    TBC - to be completed w/ some code walk through in a bit. For now, enjoy the short clips (links below):

    1. First clip shows a warm boot of the code right after resetting and clearing the display (GPS receiver already locked in - Fix value > 0 - with more than 4..5 satellites discovered and position sufficiently accurate calculated). Display of velocity not yet implemented.
    2. Second clip shows a brief sequence parking on my drive way after having the 'contraption' taken for a spin to work and back. It just behaved pretty well.


    5 Attachments

About

Avatar for allObjects @allObjects started