-
-
@Gordon, thank you! Now I see jsiLastIdleTime change!
May be I had written wrong search template.@Wilberforce, it looks like it is easiest way to call jswrap_interactive_setTime(). Or may be to implement similar own function if you prefer to set system time with millisecond precision.
-
@Gordon, @Wilberforce
Now I do not understand why some of my code still works. :)I see that jsiLastIdleTime should be changed when we change system time (by any way) to intervals remain in correct state.
But I do not see the change in code, so I understand why setSNTP does not work, but I do not understand why intervals are not broken after I call setTime() in JS. May be it happens (by some unclear way) because I call it from interval or timeout callback?
Now it seems to me there is may be a big general bug in Espruino. What do you think about that? -
-
Isn't this because when the espruino starts its at time 0 - 1970 and then you are setting the time - so the intervals immediately expire?
Yes. And settimeofday() does not correct them, so they try to catch up the time.
So we need to change it to jshSetSystemTime(), I think. SNTP app allows us to redefine function used to set system time. But I am not sure how to do it with minimal changes.What if you set the time first and then set the intervals?
It depends. Because SNTP subsystem is asynchronous and so time may be really changed before or after intervals setup. It checks connection, for example, and repeats NTP request every few minutes or every hour.
-
I've found bug in setSNTP implementation. Now I have simple example. Also I think I know reason.
var wifi=require("Wifi"); var i1=setInterval(()=>console.log(1),2000); var i2=setInterval(()=>console.log(2),3333); setTimeout(()=>{ wifi.setSNTP('pool.ntp.org','0'); },7000);
After setSNTP call both intervals works as if they are with zero delay.
I think it happens because SNTP subsystem uses settimeofday() inside by default and so does not change interval times.
It is possible to redefine SNTP_SET_SYSTEM_TIME macro to call jshSetSystemTime() instead of settimeofday(). I think it will repair the bug. I think also lwipopts.h is good place for it. -
I've located conditions when the bug appears.
It happens after setSNTP call. Some intervals start to act like they have zero interval after the call. While dump() shows actual interval data is still correct.
Broken interval list is different time to time. Sometimes all intervals become spoiled, sometimes only one or two, sometimes no one. But it happens often, each second call or about. changeInterval() call can fix broken interval.
I've returned js implementation of SNTP to my code, and now everytrhing works.Now I'm sure there is a bug in Espruino for ESP32. Unfortunately, I did not find sntp implementation in git repository (while I do not think I will find the bug but I'd want to try). In any case I will be happy to help somebody to locate and kill the bug.
I've tried to write small test code with only one interval and setSNTP but have no success - the bug does not appear in the environment, so it is not so easy to catch it.
Could anybody also explain me correct way to report the bug? It seems to me it may be a wellknown Github feature. But I did not used Github so I do not know how it is usually done.By the way, I see signs that there is may be one more bug - my code hangs after a short time (usually a few hours) after I have added many console.log() calls (so it is called about 2 times per second now). There is no special console messages before the hang. It seems that the hang appears much faster if ESP32 board is connected via USB (so ESP32 COM port is used for logging). But I did not test it enough.
-
@allObjects, do you mean that 'Direct to flash' saving method with devices initialization at global scope has disadvantages? I think both methods lead to close results...
-
Usually I save my code using 'Direct to Flash' mode in WebIDE. I do reset(1) before.
I have used setInterval(func,1000) at global scope in my code, and I have not clearInterval. The setInterval returns 1.
The code works with previous FW (v1.97).
I see some strange behavior of my code with Travis build I have got a week ago. Most often I see fast screen change (screens are swithed in the setInterval) and strange state of Wifi connection after about an hour of work. I think I can remember git hash of the build if you need it.
Today I've tried to call changeInterval(1,1000) from console and it helps - clock returns to normal work and works 2 more hours.
So it seems to me it looks like setImterval-related memory corruption or something similar (may be data in hardware timer corruption or somethig else). And it looks to be related to the FW version. -
@Wilberforce, I have some strange problems with the firmware version. For example, a few times my clock begins to change screens very fast. I've checked that function inside setInterval is called many times per second (instead of one time per second) and does it with a very different intervals. I have tried to check if setInterval was called twice (while it is called only one time in my code, of course) and call clearInterval(1) from console (I have checked that 1 is correct number) and screen change stops - so it was one interval with the strange behavior. A few times I saw another problems, I can not identify them as well as previous one. Common part is that these problems is that they begins after a few minutes or an hour of device work. I have checked memory - and more then a half is free. May be, setInterval interferes with neopixel or with other intervals?
I have another board connected to Neopixel LED sting and very simple code on it and still have no problems (last 4 hours). So tomorrow I will try to exchange these boards to check if hardware problems are. But clock worked well with old FW version on the same board.
Now I have no idea how to locate the bug. I still have not tried to connect JTAG to ESP32 (and even have not tried to build something for ESP32) but do not think it is too hard for me. So I am going to try to (may be) search for something at weekend. -
I've cleaned my code for big clock, now it is more regular. At the same time @Wilberforce and @JumJum have improved ESP32 build. So I've just cleaned my code even more by removing unnecessary MQTT and NTP implementations.
Now the code works on newest Travis build for ESP32. I think it will work on v2.01 as well.
If you do not need Russian months names just remove var MC and its usage - it is just names mapping.
Additional fields on display may get data from http requests or MQTT channel bigclock/in/temp_1 ... temp_4 (because I use the fields to show temperatures collected by other sensors). If first symbol of the message is ', field icon is omitted. If there is no new data, '---' is shown after timeout.
The program may be easily extended to show more fields on larger display or with multiplexing.
Some ugly code is still present, mostly because I don't understand internal implementation of some components (for example, I have not found sntp_init() implementation and so do not know SNTP renewal interval and also SNTP timezone and E.setTimeZone() interaction).var DevName='bigclock'; var DevNum=1; var FullName=DevName+'_'+DevNum; var neopixel = require("neopixel"); E.setTimeZone(3); var wifi=require("Wifi"); //var tnum; function getNTP() { wifi.setSNTP("pool.ntp.org",0); // tnum=setTimeout(getNTP,24*3600*1000); } wifi.on('connected', ()=>setTimeout(getNTP,30000)); //--------------------------------------------------------------------- //----------------------- Additions to font --------------------------- //--------------------------------------------------------------------- //require("Font6x8").add(Graphics); var gt = Graphics.createArrayBuffer(16,64,24,{zigzag:true,color_order:'grb'}); gt.setRotation(3,1); var font = atob("AAAAAPoAwADAAFhw2HDQAGSS/5JMAGCW+LzSDAxSolIMEsAAPEKBAIFCPABIMOAwSAAQEHwQEAABBgAQEBAQAAIAAwwwwAB8ipKifABA/gBChoqSYgCEkrLSjAAYKEj+CADkoqKinAA8UpKSDACAgI6wwABskpKSbABgkpKUeAAiAAEmABAoRAAoKCgoKABEKBAAQIqQYAA8WqW9RDgOOMg4DgD+kpKSbAB8goKCRAD+goJEOAD+kpKCAP6QkIAAfIKCklwA/hAQEP4A/gAMAgIC/AD+EChEggD+AgICAP5AIED+AP7AMAz+AHyCgoJ8AP6QkJBgAHyChoN8AP6QmJRiAGSSkpJMAICA/oCAAPwCAgL8AOAYBhjgAPAOMA7wAMYoECjGAMAgHiDAAI6SosIA/4EAwDAMAwCB/wBAgEAAAQEBAQEBEn6SggQABCoqHgD+IiIcABwiIhQAHCIi/gAcKioYACB+oIAAGCUlPgD+ICAeAL4AAQG+AP4IFCIA/AIAPiAeIB4APiAgHgAcIiIcAD8kJBgAGCQkPwA+ECAgABIqKiQAIPwiADwCAjwAIBgGGCAAOAYIBjgAIhQIFCIAIRkGGCAAJioyIgAQboEA5wCBbhAAQIDAQIAAPFqlpUI8"); var widths = atob("BAIEBgYGBgIEBAYGAwUCBQYDBgYGBgYGBgYCAwQGBAUGBgYGBgUFBgYCBgYFBgYGBgYGBgYGBgYGBgUDBQMEBgYFBQUFBQUFBQIEBQMGBQUFBQUFBAUGBgYGBQQCBAYG"); 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\x05\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 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'}; 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); }; //---------------------------------------------------------- //-------------------- main code --------------------------- //---------------------------------------------------------- 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='---', TempX1='---', TempX2='---'; function drawFieldDefault(fieldNum) { var clr=E.HSBtoRGB(fields[fieldNum].hs.h,fields[fieldNum].hs.s,ClockB,true); gt.setColor(clr[0]/255,clr[1]/255,clr[2]/255); var text=(fields[fieldNum].text[0]=="'")? fields[fieldNum].text.substr(1,20): //No icon if started with ' (fields[fieldNum].icon+fields[fieldNum].text); gt.drawString(text,((fields[fieldNum].w-gt.stringWidth(text))/2|0)+fields[fieldNum].x,fields[fieldNum].y); } var fields=[ {x:0, y:0, w:32,hs:{h:0.6, s:1 }, view:[0,1], drawField:drawFieldDefault, icon:'' , text:'---'}, {x:32,y:0, w:32,hs:{h:0.5, s:1 }, view:[0,1], drawField:drawFieldDefault, icon:'' , text:'---'}, {x:0, y:8, w:32,hs:{h:0, s:0 }, view:[0], drawField:drawFieldDefault, icon:'\x80', text:'---', timeout:undefined}, {x:32,y:8, w:32,hs:{h:0.334,s:1 }, view:[0], drawField:drawFieldDefault, icon:'\x81', text:'---', timeout:undefined}, {x:0, y:8, w:32,hs:{h:0.26, s:0.6}, view:[1], drawField:drawFieldDefault, icon:'\x81', text:'---', timeout:undefined}, {x:32,y:8, w:32,hs:{h:0.1 ,s:0.6}, view:[1], drawField:drawFieldDefault, icon:'\x81', text:'---', timeout:undefined} ]; var currentView=0; var updateCounter=0; function updateField(fieldNum) { updateCounter++; if(updateCounter==1) 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++) if(fields[i].view.indexOf(currentView)!=-1) fields[i].drawField(i); neopixel.write(D2, gt.buffer); updateCounter=0; } },1200); } function updateSettings() { //ugly hack because updateField does not really use fieldNum updateField(-1); } function setView(vnum) { currentView=vnum; updateField(-1); } var vcnt=0; setInterval(function() { if(++vcnt==10){ vcnt=0; setView(0); } if(vcnt==7) setView(1); },1000); var oldTime, newTime; function prepareTime() { var ds=Date().toString(); newTime=ds.substr((ds[14]==' ')?15:16,5); if(oldTime != newTime) { oldTime=newTime; fields[1].text = newTime; fields[0].text = Date().getDate()+' '+MC[Date().toString().substr(4,3)]; updateField(0); updateField(1); } } setInterval(prepareTime,1000); var mqtt_server = "192.168.1.40"; // the ip of your MQTT broker var mqtt_options = { // ALL OPTIONAL - the defaults are below client_id : FullName, // the client ID sent to MQTT - it's a good idea to define your own static one based on `getSerial()` }; var mqtt=require("MQTT").create(mqtt_server,mqtt_options); mqtt.on('disconnected', function() { console.log("MQTT disconnected... reconnecting."); setTimeout(function() { var status=wifi.getDetails(); if( status.status && status.status=="connected" && !mqtt.connected ) mqtt.connect(); }, 30000); }); wifi.on('connected', ()=>{ mqtt.connect(); setTimeout(getNTP,30000); }); setInterval(()=>{ var status=wifi.getDetails(); if(!status.status || status.status != "connected") { if(tnum) { clearTimeout(tnum); tnum=undefined; } wifi.restore(); } },60000); function prepareTemp(msg,i) { if(msg[0] != "'") { let Temp=parseFloat(msg); fields[1+i].text=((Temp>0)?'+':'')+Temp.toFixed(1); } else fields[1+i].text=msg.substr(0,20); if(fields[1+i].timeout) clearTimeout(fields[1+i].timeout); fields[1+i].timeout=setTimeout((i)=>{fields[1+i].text='---'; fields[1+i].timeout=undefined;updateField(1+i);},300000,i); updateField(1+i); } neopixel.write(D2, gt.buffer); neopixel.write(D2, gt.buffer); var http=require("http"); 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/libs/jscolor/2.0.4/jscolor.min.js"></script>'); 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:\'66ccff\'}" 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="?lam p_brightness='+i+'">'+i+'</a>'); res.write('</div>'); res.end('</body></html>'); if (a.query) { for(let i=1;i<=fields.length-2;i++) if("temp_"+i in a.query) { prepareTemp(a.query["temp_"+i],i); } if("lamp_brightness" in a.query) { LampBrightness=E.clip(parseInt(a.query.lamp_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(onPageRequest).listen(80); mqtt.on('connected', function() { mqtt.subscribe([ DevName+"/in/#", FullName+"/in/#" ]); }); mqtt.on('publish', function (pub) { console.log("topic: "+pub.topic); console.log("message: "+pub.message); var command=pub.topic.split("/",3)[2]; var i; for(i=1;i<=fields.length-2;i++) if(command == "temp_"+i) prepareTemp(pub.message,i); if(command == "lamp_brightness") { LampBrightness=E.clip(parseInt(pub.message),0,10); LampB =(Math.pow(2,(LampBrightness -1)/9*8)+LampBrightness /4)/256;//add linear component to be better near brightness=1 updateSettings(); } else if(command == "clock_brightness") { ClockBrightness=E.clip(parseInt(pub.message),0,10); ClockB=(Math.pow(2,(ClockBrightness-1)/9*8)+ClockBrightness/4)/256;//add linear component to be better near brightness=1 updateSettings(); } else if(command == "eval") { setTimeout( function (s) { var res=eval(s); console.log(res); mqtt.publish(DevName+'/out/eval', JSON.stringify(res)); updateSettings(); },0,pub.message); } }); //---------- auto brightness ---------- setInterval(function() { var newCB=ClockBrightness; if(newTime=="07:00") newCB=3; if(newTime=="22:00") newCB=1; if(newCB != ClockBrightness) { ClockBrightness=newCB; ClockB=(Math.pow(2,(ClockBrightness-1)/9*8)+ClockBrightness/4)/256;//add linear component to be better near brightness=1 updateSettings(); } },60000); //---------------- init --------------- if(wifi.getDetails().status=="connected") { getNTP(); mqtt.connect(); }
-
@Wilberforce, @JumJum, thanks a lot. I have just tried new Travis and it works well. Moreover, it looks to be more stable then 1v97, both Wifi and Neopixel.
-
Hi!
I have reworked my simple clock to work with MQTT server. It is not so simple now :)
It gets additional fields to display from MQTT server, while a few devices send data to the server and JS agent works on the server and converts incoming data from the sensors to data for clock.
One of the sensors is a controller of very small greenhouse. It measures temperature, turns on and off heating and light and sends its state to MQTT server. It is ESP32 board. (I put it in place of old MSP32-based controller because the old one has no wireless communications).
Everything works. But the controller becomes invisible to other devices on my local network in a small period after power on. I can connect to it and can ping it from MQTT server but not from any other devices.
I've captured some packets in my network using tcpdump and have found that the controller does not answer to ARP requests. I can not check if it receives the requests.
I have a few other ESP32 devices with the same version of Espruino (v2.00) and they are visible. One possible difference is that the controller is at longest distance (RSSI is usually about -85).
It looks strange, while is not a problem. Why it may be? -
@allObjects. You are right, 1 second noise at reset or power up (and even at not every reset or power up) is really not a big problem. So I wanted to kill it easy or to leave it as is. double write at startup seems to be easy enough to implement it. Sorry, the code looks strange and confusing. If you remove the double write (both of them) you will see no any changes most times even at power up/reset.
But if you send new code to clock every minute :) in dark room :)) it may be interesting to use the writes to remove bright noise. -
@Robin My English is not good enough, so sometimes I am not sure I see that you write. And that is the only I mean in my previous post. (And I am also not sure I write exactly that I think because of the same reason, so I am sorry :) )
I call neopixel.write twice only at startup. Besides that there is only one point where it called. Moreover, the code contains some trivial event aggregator to minimize number of calls.
When I write about synchronization I mean that WS1812 may be in any state when the code starts. Particularly, it may be still in process of byte receiving. For example, if we press reset button on ESP32 module when LED is in the process. In the state LED will interpret new data incorrectly and we will see number of bright pixels on display until next write. First regular write (current time etc) will be only after about 1 second after power on or reset, and display will be ill all the time. To minimize time of the effect I call neopixel.write twice at start. -
@Gordon I' ve found something to improve in MQTT module.
In some cases call of underlying require("net").connect(...) throws exception. mqo.emit('disconnected') is not called in such case. So general reconnection algorithm (reconnect on disconnected) is not working.
So may be it is a good idea to catch the exception in MQTT module and emit disconnected event:try { client = require("net").connect({host: mqo.server, port: mqo.port}, onConnect); } catch (e){ client = false; this.emit('disconnected'); }
It will simplify usage of MQTT module.
-
@Robin sorry, I am not sure I understand your question.
I've created global buffer to use. At startup it is blank, and I send it to display twice (one to synchronize and one to blank display while startup proceed). Then I use the buffer to write time etc.
If display is not synchronized it can show some noise after first data out, sometimes the noise is too bright. -
Newer clock:
@Robin I am a dinosaur who did not use file hosting services before. So after you explain an idea it is easy to use. About to identical lines - ws2812 can be in any state at start. Sometimes (rarely) it is in the middle of byte receiving. And one write is not enough to blank display. I think it is enough to send 1 byte (or may be 3 bytes) to sync, but it was easier to send all the buffer instead. -
-
@Gordon Thank you, now I am sure it does not seem to me. I have not checked neopixel in ESP32 v2.00. I have read it was broken in v1.98 and is not working now.
In any case the old version bug is not a problem for my clock.By the way, I've found one minor bug in MQTT module:
in MQTT.prototype._scktClosedif (this.ctimo) clearInterval(this.ctimo);
should be clearTimeout
-
-
-
Now I try to connect MQTT client to my clock project. I have found that Espruino MQTT module does not work, because its onConnect() function is called twice when it is connected to server. I have just added workaround into the function and everything is working.
Is it a bug in net module in the version or something else?I can add my code here, of course, but I think it is trivial - I just call require() and then connect().
Workaround in first line of onConnect() is to check if #onend is already registered in socket and return if it is. -
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,{zigzag:true,color_order:'grb'}); gt.setRotation(3,1); var font = atob("AAAAAPoAwADAAFhw2HDQAGSS/5JMAGCW+LzSDAxSolIMEsAAPEKBAIFCPABIMOAwSAAQEHwQEAABBgAQEBAQAAIAAwwwwAB8ipKifABA/gBChoqSYgCEkrLSjAAYKEj+CADkoqKinAA8UpKSDACAgI6wwABskpKSbABgkpKUeAAiAAEmABAoRAAoKCgoKABEKBAAQIqQYAA8WqW9RDgOOMg4DgD+kpKSbAB8goKCRAD+goJEOAD+kpKCAP6QkIAAfIKCklwA/hAQEP4A/gAMAgIC/AD+EChEggD+AgICAP5AIED+AP7AMAz+AHyCgoJ8AP6QkJBgAHyChoN8AP6QmJRiAGSSkpJMAICA/oCAAPwCAgL8AOAYBhjgAPAOMA7wAMYoECjGAMAgHiDAAI6SosIA/4EAwDAMAwCB/wBAgEAAAQEBAQEBEn6SggQABCoqHgD+IiIcABwiIhQAHCIi/gAcKioYACB+oIAAGCUlPgD+ICAeAL4AAQG+AP4IFCIA/AIAPiAeIB4APiAgHgAcIiIcAD8kJBgAGCQkPwA+ECAgABIqKiQAIPwiADwCAjwAIBgGGCAAOAYIBjgAIhQIFCIAIRkGGCAAJioyIgAQboEA5wCBbhAAQIDAQIAAPFqlpUI8"); var widths = atob("BAIEBgYGBgIEBAYGAwUCBQYDBgYGBgYGBgYCAwQGBAUGBgYGBgUFBgYCBgYFBgYGBgYGBgYGBgYGBgUDBQMEBgYFBQUFBQUFBQIEBQMGBQUFBQUFBAUGBgYGBQQCBAYG"); 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\x05\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,fields[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[fieldNum].x,fields[fieldNum].y); } fields[0].drawField=drawDate; function drawTime(fieldNum) { var clr=E.HSBtoRGB(fields[fieldNum].hs.h,fields[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[fieldNum].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,fields[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[fieldNum].x,fields[fieldNum].y); } fields[2].drawField=drawTempIn; function drawTempOut(fieldNum) { var clr=E.HSBtoRGB(fields[fieldNum].hs.h,fields[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[fieldNum].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/libs/jscolor/2.0.4/jscolor.min.js"></script>'); 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:\'66ccff\'}" 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.toFixed(1); // console.log(toTempIn); if(toTempIn) clearTimeout(toTempIn); toTempIn=setTimeout(()=>{TempIn='---'; toTempIn=undefined;updateField(1);},300000); updateField(2); } if("temp_out" in a.query) { TempOut=parseFloat(a.query.temp_out); TempOut=((TempOut>0)?'+':'')+TempOut.toFixed(1); if(toTempOut) clearTimeout(toTempOut); toTempOut=setTimeout(()=>{TempOut='---'; toTempOut=undefined;updateField(2);},300000); updateField(3); } if("lamp_brightness" in a.query) { LampBrightness=E.clip(parseInt(a.query.lamp_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(onPageRequest).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-client. 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.
I hope you can. I am not sure.