You are reading a single comment by @SergeP and its replies. Click here to read the full conversation.
  • Sorry, I do not understand how to upload a picture - 'Upload a file' in the message form allows to choose a file but nothing happens after.

    By the way, I have assembled bigger clock with 4 16x16 LED panels and ESP32 as controller. Code was rewritten for the clock:

    var neopixel = require("neopixel");
    E.setTimeZone(3);
    var wifi=require("Wifi");
    
    //--------------------------------------­------------------------
    //--------------------- NTP client -----------------------------
    //--------------------------------------­------------------------
    
        var dgram = require('dgram');
    
        defaultNtpPort = 123;
        defaultNtpServer = "pool.ntp.org";
    
        /**
         * Amount of acceptable time to await for a response from the remote server.
         * Configured default to 10 seconds.
         */
        ntpReplyTimeout = 10000;
    
        /**
         * Fetches the current NTP Time from the given server and port.
         * @param {string} server IP/Hostname of the remote NTP Server
         * @param {number} port Remote NTP Server port number
         * @param {function(Object, Date)} callback(err, date) Async callback for
         * the result date or eventually error.
         */
        getNetworkTime = function (server, port, callback) {
            if (callback === null || typeof callback !== "function") {
                return;
            }
    
            server = server || defaultNtpServer;
            port = port || defaultNtpPort;
    
            var client = dgram.createSocket("udp4"),
                ntpData = new Uint8Array(48);
    
            // RFC 2030 -> LI = 0 (no warning, 2 bits), VN = 3 (IPv4 only, 3 bits), Mode = 3 (Client Mode, 3 bits) -> 1 byte
            // -> rtol(LI, 6) ^ rotl(VN, 3) ^ rotl(Mode, 0)
            // -> = 0x00 ^ 0x18 ^ 0x03
            ntpData[0] = 0x1B;
    
    //        for (var i = 1; i < 48; i++) {
    //            ntpData[i] = 0;
    //        }
    
            var timeout = setTimeout(function () {
                client.close();
                callback("Timeout waiting for NTP response.", null);
            }, ntpReplyTimeout);
    
            // Some errors can happen before/after send() or cause send() to was impossible.
            // Some errors will also be given to the send() callback.
            // We keep a flag, therefore, to prevent multiple callbacks.
            // NOTE : the error callback is not generalised, as the client has to lose the connection also, apparently.
            var errorFired = false;
            client.bind(8215+((Math.random()*100)|0)­, function(res){
                res.on('error', function (err) {
                    if (errorFired) {
                        return;
                    }
    
                    callback(err, null);
                    errorFired = true;
    
                    clearTimeout(timeout);
                });
    
                res.on('message', function (msg) {
                    clearTimeout(timeout);
                    client.close();
    
                    // Offset to get to the "Transmit Timestamp" field (time at which the reply
                    // departed the server for the client, in 64-bit timestamp format."
                    var offsetTransmitTime = 40,
                        intpart = 0,
                        fractpart = 0;
    
                    msg=E.toUint8Array(msg);
                    // Get the seconds part
                    for (var i = 0; i <= 3; i++) {
                        intpart = 256 * intpart + msg[offsetTransmitTime + i];
                    }
    
                    // Get the seconds fraction
                    for (i = 4; i <= 7; i++) {
                        fractpart = 256 * fractpart + msg[offsetTransmitTime + i];
                    }
    
                    var milliseconds = (intpart * 1000 + (fractpart * 1000) / 0x100000000);
                    milliseconds+=-2208988800000;
                    // **UTC** time
                    var date = new Date(milliseconds);
    console.log('Date received', date.toString());
                    callback(null, date);
                });
                res.on('close', function(error) {
                    console.log('drgam is closed, error',error);
                });
            });
    //        client.on('error', function (err) {
    //            if (errorFired) {
    //                return;
    //            }
    
    //            callback(err, null);
    //            errorFired = true;
    
    //            clearTimeout(timeout);
    //        });
    
            client.send(E.toString(ntpData), 0, ntpData.length, port, server, function (err) {
                if (err) {
                    if (errorFired) {
                        return;
                    }
                    clearTimeout(timeout);
                    callback(err, null);
                    errorFired = true;
                    client.close();
                    return;
                }
    
            });
        };
    
    //--------------------------------------­------------------------
    //-- change font6x8 to add a few icons and some Russian chars --
    //--------------------------------------­------------------------
    
    //require("Font6x8").add(Graphics);
    var gt = Graphics.createArrayBuffer(16,64,24,{zig­zag:true,color_order:'grb'});
    gt.setRotation(3,1);
    var font = atob("AAAAAPoAwADAAFhw2HDQAGSS/5JMAGCW+L­zSDAxSolIMEsAAPEKBAIFCPABIMOAwSAAQEHwQEA­ABBgAQEBAQAAIAAwwwwAB8ipKifABA/gBChoqSYg­CEkrLSjAAYKEj+CADkoqKinAA8UpKSDACAgI6wwA­BskpKSbABgkpKUeAAiAAEmABAoRAAoKCgoKABEKB­AAQIqQYAA8WqW9RDgOOMg4DgD+kpKSbAB8goKCRA­D+goJEOAD+kpKCAP6QkIAAfIKCklwA/hAQEP4A/g­AMAgIC/AD+EChEggD+AgICAP5AIED+AP7AMAz+AH­yCgoJ8AP6QkJBgAHyChoN8AP6QmJRiAGSSkpJMAI­CA/oCAAPwCAgL8AOAYBhjgAPAOMA7wAMYoECjGAM­AgHiDAAI6SosIA/4EAwDAMAwCB/wBAgEAAAQEBAQ­EBEn6SggQABCoqHgD+IiIcABwiIhQAHCIi/gAcKi­oYACB+oIAAGCUlPgD+ICAeAL4AAQG+AP4IFCIA/A­IAPiAeIB4APiAgHgAcIiIcAD8kJBgAGCQkPwA+EC­AgABIqKiQAIPwiADwCAjwAIBgGGCAAOAYIBjgAIh­QIFCIAIRkGGCAAJioyIgAQboEA5wCBbhAAQIDAQI­AAPFqlpUI8");
    var widths = atob("BAIEBgYGBgIEBAYGAwUCBQYDBgYGBgYGBg­YCAwQGBAUGBgYGBgUFBgYCBgYFBgYGBgYGBgYGBg­YGBgUDBQMEBgYFBQUFBQUFBQIEBQMGBQUFBQUFBA­UGBgYGBQQCBAYG");
    font+='\x08\x1E\x32\x52\x32\x1E\x08';
    font+='\x41\x93\xAF\x79\xA1\x91\x41';
    widths+='\x07\x07';
    font+='\x62\x94\x98\x90\xFE\x00';
    font+='\x3E\x08\x08\x3E\x00';
    font+='\x3E\x2A\x2A\x14\x00';
    font+='\x78\x84\xFE\x84\x78\x00';
    font+='\x3E\x20\x20\x3E\x00';
    font+='\xFE\x0C\x30\x40\xFE\x00';
    font+='\x3E\x08\x1C\x22\x1C\x00';
    font+='\x06\x18\x20\x3E\x00';
    font+='\x3E\x20\x20\x20\x00';
    font+='\x3E\x08\x14\x22\x00';
    font+='\x20\x3E\x20\x00';
    font+='\x12\x2C\x28\x3E\x00';
    font+='\x03\x7E\x82\x82\xFE\x03\x00';
    widths+='\x06\x05\x05\x06\x05\x06\x06\x0­5\x05\x05\x04\x05\x07';
    
    //8  NNNN            NNN       N   N                                 NNN
    //4 N   N           N N N      N  NN                                N  N
    //2 N   N N  N NNN  N N N NNNN N N N N  N    NN NNNN N  N NNN  NNN  N  N
    //1  NNNN N  N N  N N N N N  N N N N N N N  N N N    N N   N  N  N  N  N
    //8   N N NNNN NNN  N N N N  N NN  N NNN N  N N N    NN    N   NNN  N  N
    //4  N  N N  N N  N  NNN  N  N NN  N N N N N  N N    N N   N   N N  N  N
    //2 N   N N  N NNN    N   N  N N   N N  N  N  N N    N  N  N  N  N NNNNNN
    //1                                                                N    N
    //    82   83   84    85   86    87   88    89   8A   8B   8C  8D    8E
    
    // Lookup table to convert English names of months to Russian using added symbols 
    var MC={'Jan':'\x82\x83\x84', 'Feb':'\x85e\x84', 'Mar':'Map', 'Apr':'A\x86p', 'May':'Ma\x8D',
            'Jun':'\x87\x88\x83', 'Jul':'\x87\x88\x89', 'Aug':'A\x84\x8A', 'Sep':'Ce\x83',
            'Oct':'O\x8B\x8C', 'Nov':'Ho\x8D', 'Dec':'\x8Ee\x8B'};
    
    //--------------------------------------­-----------
    //-------- Main program ---------------------------
    //--------------------------------------­-----------
    
    
    Graphics.prototype.setFont6x8 = function() {
        this.setFontCustom(font, 32, widths, 8);
    };
    
    Graphics.prototype.convertColorFromHTML = function(HTMLColor) {
        return parseInt(HTMLColor.substr(5,2)+HTMLColor­.substr(1,2)+HTMLColor.substr(3,2),16);
    };
    
    gt.setFont6x8();
    
    var br,d;
    
    var ClockBrightness=2,LampBrightness=0;
    var ClockB=(Math.pow(2,(ClockBrightness-1)/9­*8)+ClockBrightness/4)/256;//add linear component to be better near brightness=1
    var LampB =(Math.pow(2,(LampBrightness -1)/9*8)+LampBrightness /4)/256;//add linear component to be better near brightness=1
    
    var TempIn='---', TempOut='---';
    
    var fields=[
      {x:0, y:0, w:32,hs:{h:0.6,  s:1}, drawField:0},
      {x:32,y:0, w:32,hs:{h:0.5,  s:1}, drawField:0},
      {x:0, y:8, w:32,hs:{h:0,    s:0}, drawField:0},
      {x:32,y:8, w:32,hs:{h:0.334,s:1}, drawField:0}
    ];
    
    var updateCounter=0;
    function updateField(fieldNum) {
      updateCounter++;
      setTimeout(function() {
        if(updateCounter) {
          var clr=E.HSBtoRGB(0,0.01,LampB,true);
          gt.setBgColor(clr[0]/255,clr[1]/255,clr[­2]/255);
          gt.clear();
    
          for(let i=0;i<fields.length;i++)
            fields[i].drawField(i);
          neopixel.write(D2, gt.buffer);
          updateCounter=0;
        }
      },0);
    }
    
    function updateSettings() {
      //ugly hack because updateField does not really use fieldNum
      updateField(0);
    }
    
    var oldTime, newTime, newDate;
    
    function drawDate(fieldNum) {
      var clr=E.HSBtoRGB(fields[fieldNum].hs.h,fie­lds[fieldNum].hs.s,ClockB,true);
      gt.setColor(clr[0]/255,clr[1]/255,clr[2]­/255);
      var text=newDate;
      gt.drawString(text,((fields[fieldNum].w-­gt.stringWidth(text))/2|0)+fields[fieldN­um].x,fields[fieldNum].y);
    }
    fields[0].drawField=drawDate;
    
    function drawTime(fieldNum) {
      var clr=E.HSBtoRGB(fields[fieldNum].hs.h,fie­lds[fieldNum].hs.s,ClockB,true);
      gt.setColor(clr[0]/255,clr[1]/255,clr[2]­/255);
      var text=newTime;
      gt.drawString(text,((fields[fieldNum].w-­gt.stringWidth(text))/2|0)+fields[fieldN­um].x,fields[fieldNum].y);
    }
    fields[1].drawField=drawTime;
    
    function prepareTime() {
      var ds=Date().toString();
      newTime=ds.substr((ds[14]==' ')?15:16,5);
      if(oldTime != newTime) {
        oldTime=newTime;
        newDate = Date().getDate()+' '+MC[Date().toString().substr(4,3)];
        updateField(0);
        updateField(1);
      }
    }
    
    function drawTempIn(fieldNum) {
      var clr=E.HSBtoRGB(fields[fieldNum].hs.h,fie­lds[fieldNum].hs.s,ClockB,true);
      gt.setColor(clr[0]/255,clr[1]/255,clr[2]­/255);
      var text='\x80'+TempIn;
      gt.drawString(text,((fields[fieldNum].w-­gt.stringWidth(text))/2|0)+fields[fieldN­um].x,fields[fieldNum].y);
    }
    fields[2].drawField=drawTempIn;
    
    function drawTempOut(fieldNum) {
      var clr=E.HSBtoRGB(fields[fieldNum].hs.h,fie­lds[fieldNum].hs.s,ClockB,true);
      gt.setColor(clr[0]/255,clr[1]/255,clr[2]­/255);
      var text='\x81'+TempOut;
      gt.drawString(text,((fields[fieldNum].w-­gt.stringWidth(text))/2|0)+fields[fieldN­um].x,fields[fieldNum].y);
    }
    fields[3].drawField=drawTempOut;
    
    //updateSettings();
    setInterval(prepareTime,1000);
    
    var tnum;
    function getNTP() {
      getNetworkTime(defaultNtpServer, defaultNtpPort, function (err, date) {
        if (err) {
          console.log(err);
          tnum=setTimeout(getNTP,120000);
          return;
        }
        console.log(date);
        setTime(date.valueOf()/1000);
        tnum=setTimeout(getNTP,24*3600*1000);
      });
    }
    
    wifi.on('connected', ()=>setTimeout(getNTP,30000));
    
    setInterval(()=>{
      var status=wifi.getDetails();
      if(!status.status || status.status != "connected")
        gti.drawString(status.status,0,0);
      if(!status.status || status.status != "connected")
    //  if(["off", "wrong_password", "bad_password", "no_ap_found", "connect_fail"].indexOf(status.status)!=­-1)
      {
        if(tnum) {
          clearTimeout(tnum);
          tnum=undefined;
        }
        wifi.restore();
      }
    },300000);
    
    neopixel.write(D2, gt.buffer);
    neopixel.write(D2, gt.buffer);
    
    var http=require("http");
    
    var toTempIn,toTempOut;
    
    function onPageRequest(req, res) {
    //  console.log('pr',req.url,Date().toString­());
      var a = url.parse(req.url, true);
      res.writeHead(200, {'Content-Type': 'text/html'});
      res.write('<html><body>');
      res.write('<script src="https://cdnjs.cloudflare.com/ajax/l­ibs/jscolor/2.0.4/jscolor.min.js"></scri­pt>');
      res.write('<script>function update(hsv) { location.search="?time_color_h="+hsv[0]+­"&time_color_s="+hsv[1];}</script>'); 
      res.write('<button class="jscolor {mode:\'HS\',valueElement:null,value:\'6­6ccff\'}" style="border:2px solid black" onchange="update(this.jscolor.hsv)">Pick­ a color</button>');
    //  ,value:\'66ccff\'
    //  res.write('<p>Pin is '+(BTN.read()?'on':'off')+'</p>');
    //  res.write('<div>Mode: <a href="?mode=lamp">Lamp</a> <a href="?mode=clock">clock</a></div>');
      res.write('<div>Clock brightness: ');
      for(let i=0; i<=10; i++)
        res.write(' <a href="?clock_brightness='+i+'">'+i+'</a>­');
      res.write('</div>');
      res.write('<div>Lamp brightness: ');
      for(let i=0; i<=10; i++)
        res.write(' <a href="?lamp_brightness='+i+'">'+i+'</a>'­);
      res.write('</div>');
      res.end('</body></html>');
      if (a.query) {
        if("temp_in" in a.query) {
          TempIn=parseFloat(a.query.temp_in);
          TempIn=((TempIn>0)?'+':'')+TempIn.toFixe­d(1);
    //      console.log(toTempIn);
          if(toTempIn) clearTimeout(toTempIn);
          toTempIn=setTimeout(()=>{TempIn='---'; toTempIn=undefined;updateField(1);},3000­00);
          updateField(2);
        }
        if("temp_out" in a.query) {
          TempOut=parseFloat(a.query.temp_out);
          TempOut=((TempOut>0)?'+':'')+TempOut.toF­ixed(1);
          if(toTempOut) clearTimeout(toTempOut);
          toTempOut=setTimeout(()=>{TempOut='---';­ toTempOut=undefined;updateField(2);},300­000);
          updateField(3);
        }
        if("lamp_brightness" in a.query) {
          LampBrightness=E.clip(parseInt(a.query.l­amp_brightness),0,10);
          LampB =(Math.pow(2,(LampBrightness -1)/9*8)+LampBrightness /4)/256;//add linear component to be better near brightness=1
          updateSettings();
        }
        if("clock_brightness" in a.query) {
          ClockBrightness=E.clip(parseInt(a.query.­clock_brightness),0,10);
          ClockB=(Math.pow(2,(ClockBrightness-1)/9­*8)+ClockBrightness/4)/256;//add linear component to be better near brightness=1
          updateSettings();
        }
      }
    //  console.log('pr2',Date().toString());
    }
    
    require("http").createServer(onPageReque­st).listen(80);
    
    if(wifi.getDetails().status=="connected"­) getNTP();
    
    

    Clock screen is divided to 4 fields - date, time and 2 temperatures which can be set by http now, but I am going to change it to MQTT client.
    Clock brightness may be set by http too. Field colors too, but it does not fully work still.
    ESP32 v1.97 (last known to work with neopixel) looks like does not have SNTP included. So I have added NTP client from https://github.com/moonpyk/node-ntp-clie­nt. I have modified the code to it work in Espruino environment. I hope its MIT license allows to do that. I am not sure what should I write here about the client.
    I have found strange behavior of Espruino Date() while I have worked on the library: dates about 1900-1905 are incorrecly converted to milliseconds. So I use exact number of milliseconds but not Date("01.01.1900") or something similar in NTP time conversion. I have not found the effect in node.js so may be it is a bug in Espruino.

About

Avatar for SergeP @SergeP started