Avatar for user6350

user6350

Member since Jan 2014 • Last active Aug 2015
  • 3 conversations
  • 36 comments

Most recent activity

  • in General
    Avatar for user6350

    I did a check executing a same program from ( downloading from the Web IDE ) or from FLASH using the USB console.
    I obtained that the version executed after downloading from Flash onInit() runs twice as much as it would.
    I set a led flashing at 1 Hz.
    It flashed at 1 Hz when executing after a reset() and at 2Hz when executing after onInit()

    function SD_flash(op) {
       if ( op == "start") {
         if ( SD_TIM === undefined ) {
           console.log("SD_TIM");
           SD_TIM = setInterval(function() {
              sd_on = !sd_on; if (sd_on) { STOP.set(); } else {STOP.reset();}}, 1000);
         }
       }
       if ( op == "stop") {
         if (SD_TIM !== undefined) { clearInterval(SD_TIM); SD_TIM=undefined; setTimeout(function(){STOP.reset();},1000); }
       }
    }
    

    The same happens for characters received fron an external part and sampled every 100 ms in a state machine loop.
    IS there a reason for this behaviour ?

    Thanks in advance for the reply.

    Please find attached below the profiling.

    reset();
    =undefined
     _____                 _
    |   __|___ ___ ___ _ _|_|___ ___
    |   __|_ -| . |  _| | | |   | . |
    |_____|___|  _|_| |___|_|_|_|___|
              |_| http://espruino.com
     1v63 Copyright 2014 G.Williams
    >echo(0);
    =undefined
    ERROR: Unable to mount SD card : NOT_READY
    SD_TIM
    UART5: ARF54 Command mode request.
    Received 0
    Elapsed: 200 ms
    STEP 1
    Elapsed: 300 ms
    STEP 2
    Elapsed: 700 ms
    Received 0
    Elapsed: 700 ms
    STEP 3
    Elapsed: 800 ms
    Received 0
    Elapsed: 900 ms
    STEP 4
    Elapsed: 1100 ms
    Received 0
    Elapsed: 1200 ms
    STEP 5
    Elapsed: 1300 ms
    Received 0
    Elapsed: 1400 ms
    STEP 6
    Elapsed: 1700 ms
    Received 0
    Elapsed: 1700 ms
    STEP 7
    Elapsed: 1800 ms
    Received 0
    Elapsed: 1900 ms
    STEP 8
    Elapsed: 2000 ms
    Received 0
    Elapsed: 2100 ms
    STEP 9
    Elapsed: 2200 ms
    Received 0
    Elapsed: 2300 ms
    STEP 10
    Elapsed: 2400 ms
    Received 0
    Elapsed: 2700 ms
    END OF SEPRI INIT SEQUENCE.
    Elapsed: 2700 ms
    
    UART5: ARF54 Command mode request.
    Received 0
    Elapsed: 200 ms
    STEP 1
    Elapsed: 300 ms
    STEP 2
    Elapsed: 700 ms
    Received 0
    Elapsed: 800 ms
    STEP 2
    Elapsed: 800 ms
    Received 0
    Elapsed: 900 ms
    STEP 3
    Elapsed: 900 ms
    Received 0
    Elapsed: 1000 ms
    STEP 4
    Elapsed: 1100 ms
    Received 0
    Elapsed: 1200 ms
    STEP 5
    Elapsed: 1300 ms
    Received 0
    Elapsed: 1400 ms
    STEP 6
    Elapsed: 1500 ms
    Received 0
    Elapsed: 1800 ms
    STEP 6
    Elapsed: 1800 ms
    STEP 7
    Elapsed: 1800 ms
    Received 0
    Elapsed: 1800 ms
    STEP 8
    Elapsed: 1900 ms
    Received 0
    Elapsed: 2000 ms
    STEP 9
    Elapsed: 2100 ms
    Received 0
    Elapsed: 2200 ms
    STEP 10
    Elapsed: 2300 ms
    Received 0
    Elapsed: 2400 ms
    END OF SEPRI INIT SEQUENCE.
    Elapsed: 2500 ms
    
    UART5: ARF54 Command mode request.
    Received 0
    Elapsed: 400 ms
    STEP 1
    Elapsed: 2700 ms
    STEP 1
    Elapsed: 3000 ms
    STEP 2
    Elapsed: 3100 ms
    Received 0
    Elapsed: 3200 ms
    STEP 3
    Elapsed: 3300 ms
    Received 0
    Elapsed: 3400 ms
    STEP 4
    Elapsed: 3600 ms
    Received 0
    Elapsed: 3700 ms
    STEP 5
    Elapsed: 4000 ms
    Received 0
    Elapsed: 4100 ms
    STEP 5              RICHIESTA RIPETUTA DA NIC.
    Elapsed: 4100 ms
    Received 0
    Elapsed: 4200 ms
    STEP 6
    Elapsed: 4200 ms
    Received 0
    Elapsed: 4300 ms
    STEP 7
    Elapsed: 4300 ms
    Received 0
    Elapsed: 4400 ms
                      <— SALTATO UNO STEP, ovvero una inizializzazione
    STEP 9
    Elapsed: 4500 ms
    Received 0
    Elapsed: 4600 ms
    STEP 10
    Elapsed: 4700 ms
    Received 0
    Elapsed: 4800 ms
    END OF SEPRI INIT SEQUENCE.
    Elapsed: 5000 ms
    
    
    UART5: ARF54 Command mode request.
    Received 0
    Elapsed: 200 ms
    STEP 1
    Elapsed: 300 ms
    STEP 2
    Elapsed: 500 ms
    Received 0
    Elapsed: 700 ms
    STEP 3
    Elapsed: 800 ms
    STEP 3
    Elapsed: 2100 ms  <— QUI SI INTERROMPE
    
    
    >save()
    =undefined
    Erasing Flash...
    Programming 109000 Bytes..............................................................................................................
    Checking...
    Done!
    Running onInit()...
    ERROR: Unable to mount SD card : NOT_READY
    SD_TIM
    UART5: ARF54 Command mode request.
    Received 0
    Elapsed: 0 ms
    STEP 1
    Elapsed: 0 ms
    STEP 2
    Elapsed: 200 ms
    Received 0
    Elapsed: 200 ms
    STEP 3
    Elapsed: 300 ms
    Received 0
    Elapsed: 300 ms
    STEP 4
    Elapsed: 500 ms
    Received 0
    Elapsed: 500 ms
    STEP 5
    Elapsed: 600 ms
    Received 0
    Elapsed: 600 ms
    STEP 6
    Elapsed: 700 ms
    Received 0
    Elapsed: 700 ms
    STEP 7
    Elapsed: 800 ms
    Received 0
    Elapsed: 1000 ms
    STEP 7
    Elapsed: 1000 ms
    STEP 7
    Elapsed: 1000 ms
    Received 0
    Elapsed: 1000 ms
    STEP 8
    Elapsed: 1000 ms
    Received 0
    Elapsed: 1000 ms
    STEP 9
    Elapsed: 1000 ms
    STEP 10
    Elapsed: 1000 ms
    Received 0
    Elapsed: 1100 ms
    END OF SEPRI INIT SEQUENCE.
    Elapsed: 1200 ms
    
    UART5: ARF54 Command mode request.
    Received 0
    Elapsed: 100 ms
    STEP 1
    Elapsed: 200 ms
    STEP 2
    Elapsed: 300 ms
    Received 0
    Elapsed: 300 ms
    STEP 3
    Elapsed: 400 ms
    Received 0
    Elapsed: 400 ms
    STEP 4
    Elapsed: 700 ms
    Received 0
    Elapsed: 700 ms
    STEP 5
    Elapsed: 700 ms
    Received 0
    Elapsed: 800 ms
    STEP 6
    Elapsed: 900 ms
    Received 0
    Elapsed: 900 ms
    STEP 7
    Elapsed: 1000 ms
    Received 0
    Elapsed: 1000 ms
    STEP 8
    Elapsed: 1100 ms
    Received 0
    Elapsed: 1100 ms
    
  • in Interfacing
    Avatar for user6350

    Please find here a log view corresponding to 4 different acquisitions or travels.

  • in Interfacing
    Avatar for user6350

    /Users/gv/Desktop/screenshot_667.png

  • in Interfacing
    Avatar for user6350

    File not uploaded, so I copied the code here.
    Please find here the example firmware

    //==================================================================================
    // Copyright (c) 2014 Giovanni Varasano.
    // v4 - last update 06/06/2014
    // ==================================================================================
    
    var fs = require("fs");
    
    var  ST = { IDLE: 0, FIX:1, RUN:2 };
    tmrId=0;
    file_ok=0, gps_ok = 0;
    gps_path="";
    gps_date= "", gps_time=""; gps_msec=0;
    gps_lat=0, gps_lon=0, gps_speed =0; gps_course=0;
    ir_red=0.0,ir_nir=0.0,irr_edg=0.0,ir_ndre=0.0,ir_ndvi=0.0;
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    var WORK    = LED2; 
    var FIX     = LED3;
    var STOP    = LED4; 
    
    // ---------------------------------------------------------
    // ---------------------------------------------------------
    btn_state = false;
    var btn_id = setWatch(function(e) { btn_state = !btn_state; } ,B6,{ repeat : true, edge : "both", debounce : 350});
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    function onInit() {
      setTimeout(function(){
        file_ok=0, gps_ok = 0;
        Serial6.setup(19200, {rx:C7,tx:C6});
        Serial6.print("$JBAUD,115200\r\n");
        
        setTimeout(function(){
            Serial6.setup(115200, {rx:C7,tx:C6});
            Serial4.setup(38400, {rx:C11,tx:C10});
            tracking("RMC");
            tracking("GGA");
            tmrId = go_ahead();
           }, 3000 );
        }, 2000 );
    }
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    function go_ahead() {
        state=ST.IDLE
        return  setInterval( function() {
             switch(state) {
    
                case ST.IDLE: file_ok=0;STOP.set();FIX.reset();WORK.reset();
                             if (btn_state ) { STOP.reset(); state=ST.FIX; }
    	     	         break;
    
                case ST.FIX: if (!btn_state ) {  state=ST.IDLE }
                             FIX.set();
                             if(file_ok === 1) {FIX.reset();state=ST.RUN;work=WORK.startFlashing(250);}
    	                 break;
    
                case ST.RUN: if (!btn_state ) {state=ST.IDLE; WORK.stopFlashing(work);}
                             save_data();
    	                 break;
            }
        },50); 
    }
    
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    function save_data() {
       if ( gps_ok ) {
         var s_data  = gps_date+","+gps_time+"."+gps_msec+","+gps_speed+","+gps_course+",";
         s_data += utm.toString()+","+ir_red+","+ir_nir+","+irr_edg+","+ir_ndre+","+ir_ndvi+"\r\n";
         fs.appendFileSync(gps_path,s_data);
         gps_ok=0;
       }
    }
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    function LatLng(lat,lng,alt) {
      this.lat = lat;
      this.lng = lng;
      this.alt = alt;
      this.toUTMRef = LatLngToUTMRef;
      this.toString = LatLngToString;
    }
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    function LatLngToString() {
      return "(" + this.lat + ", " + this.lng + ")";
    }
    
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    function UTMRef(easting, northing, altitude, latZone, lngZone) {
      this.easting  = Number(easting).toFixed(3);   
      this.northing = Number(northing).toFixed(3);  
      this.altitude = Number(altitude).toFixed(3); 
      this.latZone = latZone;
      this.lngZone = lngZone;
     
      this.toString = UTMRefToString;
    }
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    function UTMRefToString() {
      return this.easting + "," + this.northing+ ","+this.altitude+","+this.latZone+this.lngZone+"";
    }
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    function RefEll(maj, min) {
      this.maj = maj;
      this.min = min;
      this.ecc = ((maj * maj) - (min * min)) / (maj * maj);
    }
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    function LatLngToUTMRef() {
      var wgs84 = new RefEll(6378137, 6356752.314);
      var UTM_F0   = 0.9996;
      var a = wgs84.maj;
      var eSquared  = wgs84.ecc;
      var longitude = this.lng;
      var latitude  = this.lat;
      var altitude  = this.alt;
    
      var latitudeRad   = latitude  * (Math.PI / 180.0);
      var longitudeRad  = longitude * (Math.PI / 180.0);
      var longitudeZone = Math.floor((Number(longitude) + 180.0) / 6.0) + 1;
      
      if (latitude >= 56.0  && latitude < 64.0 && longitude >= 3.0  && longitude < 12.0) {
        longitudeZone = 32;
      }
    
      if (latitude >= 72.0 && latitude < 84.0) {
        if (longitude >= 0.0 && longitude < 9.0) {
          longitudeZone = 31;
        } else if (longitude >= 9.0 && longitude < 21.0) {
          longitudeZone = 33;
        } else if (longitude >= 21.0 && longitude < 33.0) {
          longitudeZone = 35;
        } else if (longitude >= 33.0 && longitude < 42.0) {
          longitudeZone = 37;
        }
      }
      
      var longitudeOrigin = (longitudeZone - 1) * 6 - 180 + 3;
      var longitudeOriginRad = longitudeOrigin * (Math.PI / 180.0);
    
      
      var UTMZone = getUTMLatitudeZoneLetter(latitude);
      
      ePrimeSquared = (eSquared) / (1 - eSquared);
    
      var n = a / Math.sqrt(1 - eSquared * Math.sin(latitudeRad) * Math.sin(latitudeRad));
      var t = Math.tan(latitudeRad) * Math.tan(latitudeRad);
      var c = ePrimeSquared * Math.cos(latitudeRad) * Math.cos(latitudeRad);
      var A = Math.cos(latitudeRad) * (longitudeRad - longitudeOriginRad);
    
     var M = a * (
        (1  - eSquared / 4 - 
         3 * eSquared * eSquared / 64  - 
         5 * eSquared * eSquared * eSquared / 256)  * latitudeRad  - 
       (3 * eSquared / 8  + 3 * eSquared * eSquared / 32 + 45 * eSquared * eSquared * eSquared / 1024)  * 
       Math.sin(2 * latitudeRad) + (15 * eSquared * eSquared / 256   + 45 * eSquared * eSquared * eSquared / 1024)  * 
       Math.sin(4 * latitudeRad)  - (35 * eSquared * eSquared * eSquared / 3072)* Math.sin(6 * latitudeRad));
    
     var UTMEasting =
        (UTM_F0   * n * 
         (A  + (1 - t + c) * Math.pow(A, 3.0) / 6 + 
          (5 - 18 * t + t * t + 72 * c - 58 * ePrimeSquared) * 
          Math.pow(A, 5.0)  / 120)   + 500000.0);
     
      var UTMNorthing = (UTM_F0 * (M   + n * Math.tan(latitudeRad) * 
                  (A * A / 2 + (5 - t + (9 * c) + (4 * c * c)) *
              Math.pow(A, 4.0) / 24 + (61 - (58 * t) + (t * t) + (600 * c) - 
         (330 * ePrimeSquared)) * Math.pow(A, 6.0) / 720))
       );
    
      if (latitude < 0) { UTMNorthing += 10000000.0; }
    
      return new UTMRef(UTMEasting, UTMNorthing, altitude, UTMZone, longitudeZone);
    }
    
    
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    function getUTMLatitudeZoneLetter(latitude) {
      if ((84 >= latitude) && (latitude >= 72)) return "X";
      else if (( 72 > latitude) && (latitude >=  64)) return "W";
      else if (( 64 > latitude) && (latitude >=  56)) return "V";
      else if (( 56 > latitude) && (latitude >=  48)) return "U";
      else if (( 48 > latitude) && (latitude >=  40)) return "T";
      else if (( 40 > latitude) && (latitude >=  32)) return "S";
      else if (( 32 > latitude) && (latitude >=  24)) return "R";
      else if (( 24 > latitude) && (latitude >=  16)) return "Q";
      else if (( 16 > latitude) && (latitude >=   8)) return "P";
      else if ((  8 > latitude) && (latitude >=   0)) return "N";
      else if ((  0 > latitude) && (latitude >=  -8)) return "M";
      else if (( -8 > latitude) && (latitude >= -16)) return "L";
      else if ((-16 > latitude) && (latitude >= -24)) return "K";
      else if ((-24 > latitude) && (latitude >= -32)) return "J";
      else if ((-32 > latitude) && (latitude >= -40)) return "H";
      else if ((-40 > latitude) && (latitude >= -48)) return "G";
      else if ((-48 > latitude) && (latitude >= -56)) return "F";
      else if ((-56 > latitude) && (latitude >= -64)) return "E";
      else if ((-64 > latitude) && (latitude >= -72)) return "D";
      else if ((-72 > latitude) && (latitude >= -80)) return "C";
      else return 'Z';
    }
    
    // -----------------------------------------------------------
    // --------------------------------------------------
    var Checksum = function(sentence, checksum) {
          var q, c1, c2, i;
          i = 1; 
          c1 = sentence.charCodeAt(i); 
          for( i = 2; i < sentence.length; ++i) {
            c1 = c1 ^ sentence.charCodeAt(i);
          }
          c2 = parseInt(checksum, 16);
          return (c1  === c2); 
    };
    
    // -----------------------------------------------------------
    // --------------------------------------------------
    var m_hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
    
    // -----------------------------------------------------------
    // --------------------------------------------------
    var toHexString = function(v) {
          var lsn, msn;
          msn = (v >> 4) & 0x0f;
          lsn = (v >> 0) & 0x0f;
          return m_hex[msn] + m_hex[lsn];
    };
    
    // -----------------------------------------------------------
    // --------------------------------------------------
    var computeChecksum = function(sentence) {
          var c1;
          var i;
          i = 1; 
          c1 = sentence.charCodeAt(i);
          for( i = 2; i < sentence.length; ++i) {
            c1 = c1 ^ sentence.charCodeAt(i);
          }
          return '*' + toHexString(c1);
    };
    
    
    // -----------------------------------------------------------
    // -----------------------------------------------------------
    function handleGPSLine(line ,callback) {
       var c = line.split("*");
      if ( Checksum(c[0],c[1]) ) {
          var tag = line.substr(3,3);
          var d = line.split(",");
          if (tag=="GGA") {  
             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),
                msec : (parseInt(d[1].substr(7,2),10)*10),
                lat : ((parseInt(d[2].substr(0,dlat-2),10)+parseFloat(d[2].substr(dlat-2))/60)*(d[3]=="S"?-1:1)).toFixed(9),
                lon : ((parseInt(d[4].substr(0,dlon-2),10)+parseFloat(d[4].substr(dlon-2))/60)*(d[5]=="W"?-1:1)).toFixed(9),
                fix : parseInt(d[6],10),
                alt : parseFloat(d[9])
             });
          }
          if (tag=="RMC") { 
             callback({
              tag    : tag,
              time   : d[1].substr(0,2)+":"+d[1].substr(2,2)+":"+d[1].substr(4,2),
              valid  : d[2],
              speed  : parseFloat(d[7])*1.852,
              course : parseFloat(d[8]),
              date   : d[9].substr(0,2)+"/"+d[9].substr(2,2)+"/"+d[9].substr(4,2),
              path   : d[9].substr(4,2)+d[9].substr(2,2)+d[9].substr(0,2)+"_"+d[1].substr(0,2)+d[1].substr(2,2)+".txt"
          		});
         }
      }
    }
    
    // -----------------------------------------------------------
    // --------------------------------------------------
    function fix_gps_data(data) {
       if ( data.fix > 0 ) {
            gps_ok = 1;
            gps_time=data.time;
            gps_msec=data.msec;
            gps_lat=data.lat;
            gps_lon=data.lon;
            var ll = new LatLng(data.lat,data.lon,data.alt);
            utm = ll.toUTMRef();
       } else {
            gps_ok = 0;
       } 
    }
    
    // -----------------------------------------------------------
    // --------------------------------------------------
    function get_gps_data(data) {
        if(data.tag == "GGA" ) {  fix_gps_data(data); }
        if(data.tag == "RMC" ) {  fix_data(data); }
    }
    
    // -----------------------------------------------------------
    // -----------------------------------------------------------
    function fix_data(data) {
        if ( data.valid=="A" ) {
          if ( file_ok === 0) {
    	    file_ok=1;
        	    gps_date = data.date;
        	    gps_path = data.path;
                var head = "# File: "+gps_path+" created on "+data.date+" "+data.time+"\r\n";
        	    fs.appendFileSync(gps_path,head);
          }
          gps_speed  = data.speed;
          gps_course = data.course;
        }
    }
    // -----------------------------------------------------------
    // --------------------------------------------------
    function fix_irs_data(data) {
        ir_red   = data.red;
        ir_nir   = data.nir;
        irr_edg  = data.red_edg;
        ir_ndre  = data.ndre;
        ir_ndvi  = data.ndvi; 
    }
      
    
    // ---------------------------------------------------
    //
    
    // --------------------------------------------------
    function handleIRSLine(line ,callback) {
         var d = line.split(",");
         callback({ red     : parseFloat(d[0]),
    	        nir     : parseFloat(d[1]),
                    red_edg : parseFloat(d[2]),
                    ndre    : parseFloat(d[3]),
                    ndvi    : parseFloat(d[4]) });
    }
    
    // -----------------------------------------------------------
    // --------------------------------------------------
    Serial6.onData(function (e) {  
        if (e.data=="\n") { 
          var line = gpsLine;
          gpsLine="";
          handleGPSLine(line ,function(data) { get_gps_data(data); } );
        } else gpsLine+=e.data;
      } 
    );
    
    // -----------------------------------------------------------
    // --------------------------------------------------
    Serial4.onData(function (e) {  
        if (e.data=="\n") { 
          var line = irsLine;
          irsLine="";
          handleIRSLine(line ,function(data) {  fix_irs_data(data); } );
        } else irsLine+=e.data;
      } 
    );
    
    // --------------------------------------
    // --------------------------------------
    var tracking = function(cmd ) {
      var str = "$J";
      if (cmd =="GGA" ) { 
          str += "ASC,GP"+cmd+",10";
      } else if ( cmd == "RMC" ) {
           str += "ASC,GP"+cmd+",1";
      } else { }
      if (cmd == "OFF") {  str += cmd; }
      str += "\r\n";
      Serial6.print(str);
    };
    
    // ----------------------------------------------
    // ----------------------------------------------
    Pin.prototype.startFlashing = function(period) { 
      var on = false;
      var pin = this;
      return setInterval(function() { on = !on; digitalWrite(pin, on); }, period);
    };
    
    // ----------------------------------------------
    // ----------------------------------------------
    Pin.prototype.stopFlashing = function(stop ) { 
        var pin = this;
        clearInterval(stop);digitalWrite(pin,0);
    };
    
    onInit();
    
  • in Interfacing
    Avatar for user6350

    Please find here the example firmware

  • in Interfacing
    Avatar for user6350

    I recently build a GPS data logger , based entirely on Espruino Javascript, recording data onto a SD card at a 10 Hz rate.
    Please find the relevant code here.
    No problems at all except those coming from missing fixes of GPS in "urban canyons".

  • in Interfacing
    Avatar for user6350

    You can use a moving average with an interval of 8 or 16 samples .
    I used the following code to get stable readings from a 10-bit encoder.
    Using the dithered noise resolution is in fact improved to 1 1bits.

    setInterval(function() {
            enc_deg = sma16(degree(C1).toFixed(1)); // console.log("Encoder Angle[°] = "+enc_deg.toFixed(1)); 
        }, 100 );
    function degree( pin ) {
      var ratio  = 4.0;                  
      var offset = 0.5;       
      var factor = 0.73333333333; 
      var phys   = analogRead(pin) * 3.3;  // *E.getAnalogVRef(); 
      var meas   = 360.0 * ( phys / factor - offset ) / ratio ;    
      console.log("Input[V] = "+phys.toFixed(3)+"  Angle[°] = "+meas.toFixed(1));
      return meas;
    }
    
    
    // ------------------------------------
    // simple parametric moving averager
    // ------------------------------------
    function sma(period) {
        var nums = [];
        return function(num) {                 
            nums.push(num);
            if (nums.length > period) nums.splice(0,1); 
            var sum = 0; var n = period;
            function sum_num (n) { sum+=Number(n); };
            nums.forEach(sum_num);
            if (nums.length < period) n = nums.length;
            return(sum/n);
        }
    }
    
    var sma16 = sma(16);
    

    Input[V] = 0.494 Angle[°] = 15.6 Next reading = 15.6, SMA = 15.6
    Input[V] = 0.496 Angle[°] = 15.9 Next reading = 15.9, SMA = 15.7
    Input[V] = 0.495 Angle[°] = 15.8 Next reading = 15.8, SMA = 15.8
    Input[V] = 0.495 Angle[°] = 15.8 Next reading = 15.8, SMA = 15.8
    Input[V] = 0.495 Angle[°] = 15.7 Next reading = 15.7, SMA = 15.7
    Input[V] = 0.498 Angle[°] = 16.1 Next reading = 16.1, SMA = 15.8
    Input[V] = 0.497 Angle[°] = 16.0 Next reading = 16.0, SMA = 15.8
    Input[V] = 0.495 Angle[°] = 15.7 Next reading = 15.7, SMA = 15.8
    Input[V] = 0.496 Angle[°] = 15.9 Next reading = 15.9, SMA = 15.8
    Input[V] = 0.495 Angle[°] = 15.7 Next reading = 15.7, SMA = 15.8
    Input[V] = 0.495 Angle[°] = 15.7 Next reading = 15.7, SMA = 15.8
    Input[V] = 0.494 Angle[°] = 15.6 Next reading = 15.6, SMA = 15.8
    Input[V] = 0.494 Angle[°] = 15.6 Next reading = 15.6, SMA = 15.8
    Input[V] = 0.495 Angle[°] = 15.8 Next reading = 15.8, SMA = 15.8
    Input[V] = 0.495 Angle[°] = 15.7

Actions