Adding timestamps to a wireless temperature sensor

Feature image

I've been adding updates to the wireless temperature sensor code I posted last week, mainly adding timestamps to the sensor readings.

canvas

I first tried using an array of objects of the form { time: <timestamp>, temp: <temperature>}, but for some reason the ESP8266 would reboot every couple of hours. My guess is that it was some kind of memory or buffer overrun issue, so I used two separate array (one for time, one for temperature) instead.

Getting accurate time from microcontrollers is notoriously difficult. At Tidepool we have an entire guide dedicated to working with timestamps coming from diabetes devices, and we use a pretty complex algorithm to convert local timestamps to UTC time.

Any microcontroller timer is susceptible to clock drift, so it was nice to discover that Espruino has a feature where it auto-updates the time on the device every time you push new code to it. While this is not necessarily going to work that well in real-world environments where you're not frequently pushing code changes, it works great for my prototype where I'm pushing new updates all the time.

Currently the prototype is sitting in our nursery, measuring the room temperature overnight to see if the baby is getting too hot, as Swansea is currently experiencing a very unusual spell of warm weather. I still can't get over how cool it is to program the ESP8266 over WiFi in another room two floors up.

So, how did I go about getting those timestamps? The first step is to set your timezone on the device, offset from UTC, so for the UK it would be 1 for UTC+1:

E.setTimeZone(1); // UTC + 1

Also remember to check the box next to “When sending code, set Espruino's clock to the current time” in the Espruino Web IDE. Getting the current time is then just regular JavaScript code:

var dt = new Date(Date.now());
var time = dt.getHours() + ':' + dt.getMinutes().toString().padStart(2,'0');
console.log(time, temp);

Note that on the ESP8266 I had to use the padStart polyfill as Espruino does not yet support ES8/ECMAScript 2017. Here is the entire script, taking sensor readings every 10 minutes and printing the timestamp and value every 5 readings:

var wifi = require('Wifi');
var http = require('http');

var led = Pin(NodeMCU.D4);
var toggle=1;
var ow = new OneWire(NodeMCU.D3);
var sensor = require("DS18B20").connect(ow);
var history = new Float32Array(60);
var timeArr = new Array(60);

function updateLed(){
  digitalWrite(led, toggle);
  toggle=!toggle;
}

// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
    String.prototype.padStart = function padStart(targetLength,padString) {
        targetLength = targetLength>>0; //truncate if number or convert non-number to 0;
        padString = String((typeof padString !== 'undefined' ? padString : ' '));
        if (this.length > targetLength) {
            return String(this);
        }
        else {
            targetLength = targetLength-this.length;
            if (targetLength > padString.length) {
                padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
            }
            return padString.slice(0,targetLength) + String(this);
        }
    };
}


setInterval(function() {
  updateLed();

  var temp = sensor.getTemp();
  var dt = new Date(Date.now());
  var time = dt.getHours() + ':' + dt.getMinutes().toString().padStart(2,'0');
  console.log(time, temp);
  // move history back
  for (var i = 1; i < history.length; i++) {
    history[i-1] = history[i];
    timeArr[i-1] = timeArr[i];
  }
  // insert new history at end
  history[history.length-1] = temp;
  timeArr[timeArr.length-1] = time;
}, 600000); // every 10 minutes

function onPageRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write('<html><head><meta charset="utf-8"/><meta http-equiv="refresh" content="600"></head>'+
            '<body><canvas id="canvas" width="400" height="200" style="border:1px solid #888;"></canvas><script>');
  res.write('var d='+JSON.stringify(history)+';'+
            'var t='+JSON.stringify(timeArr)+';'+
'var c=document.getElementById("canvas").getContext("2d");'+
'c.moveTo(0,100 - (d[0]-d[d.length-1])*10);'+
'for (i in d) {'+
'var x = i*400/(d.length-1); var y = 100 - (d[i]-d[d.length-1])*10;'+
'c.lineTo(x, y);'+
'if (i % 5 === 0) {'+
'c.fillText(Number.parseFloat(d[i]).toFixed(1),x,y - 5);'+
'c.fillText(t[i],x,y + 20);}}'+
'c.stroke()'+
'</script>');
  res.end('</body></html>');
}

function onInit() {
  wifi.restore();
  http.createServer(onPageRequest).listen(80);
  console.log("Server created at " + wifi.getIP().ip);
  E.setTimeZone(1); // UTC+1, remember to select "set current time" in IDE
}

onInit();

Update: Using NTP

Justin from Swansea Hackspace pointed out that I could use NTP to get the time:

I assume this is an internet enable device, so why not just use NTP? For arduino based code the standard Time library has an example of an NTP SyncProvider callback

— Justin Mitchell (@popemadmitch) June 11, 2018

I looked it up in the Espruino docs, and low and behold, it's as easy as wifi.setSNTP('uk.pool.ntp.org', 1); (for UTC+1).

#Electronics