Friday, December 7, 2018

Internet of things (IoT) temperature logger for the BeagleBone Black (Part 4)


click for Part 1
click for Part 2
click for Part 3

Introduction

In this post we will wrap this project up for now. We will:
  • Add a pair of date pickers using jQuery. These will allow us to select the data range that is uploaded from the server to form the graph etc.
  • Add a CSV file (comma separated variables) download of the data. The amount of data downloaded will also be determined by the date pickers
  • Add the necessary code for the server and the client to talk to each other about the date picker settings and about the range of dates which may be validly selected.
  • Sundry other small changes, and some cosmetic changes with CSS.

The Datepickers

After first messing about with HTML datepickers I settled on using jQuery ones, which behaved much more consistently upon changing browsers and had far more reliable control over the range of permitted dates.

The date pickers have a number of constraints that must be implemented. The date of the second picker cannot be earlier than that of the first, the dates which may be selected need to be limited by the date range in the database, and the maximum date which may be selected can grow as the database grows. All of this requires communication between the client and the server in both directions. The server does not know what state the client is in, so it is up to the client to specify the date range upon requesting data from the server.

Requesting the data

There are four different types of data requests that the client makes to the server after the webpage has loaded
  1. The client requests the time and temperature, which is displayed in the sentence near the top of the webpage, e.g. "At 09:52:01, the temperature was 27.4 degrees Celsius (Updated every 5mins)". This is now updated automatically, every 5 minutes, using the JS function setInterval().
  2. The date at which the latest temperature has been measured and stored in the database will increase as time progresses, and so we then need to update the maximum date which may be chosen from the datepicker. The setInterval() function is again used, this time to request the latest date recording in the database from the server. This is done every  999983 milliseconds which is a prime number. Thus the simultaneous execution of the two different setInterval() calls will only occur rarely, easing the load on the server.
  3. The data for the graph may be requested from the server. When this is requested the date range from the datepickers is sent to the server, and the server returns the data within this date range.
  4. The data for the downloading as a CSV file may be requested from the server. Again the data that is sent back is in the range as specified by the datepickers.

Of these four cases the latter two are more interesting, because they need to send some data to the server in order to specify what is sent back. In the case of the graph the JS fetch method is used to achieve this, and in the case of the file download ordinary HTML is used.

Fetch: graph data

The code for fetching the graph data is

    // function to renew the graph data
    updateData(){
        var x = "select * from tbl1 where time between '";
        x += this.date_smallest + "T00:00' and '" 

          + this.date_largest +"T24:00'";
        //Get the json file with fetch
        fetch(url2, {  // send date data after header
            method:'post',
            headers: {
                "Content-type": "application/dates; charset=UTF-8"
            },
            body: x
        })
        .then((response) => {
            return response.json();
        }).then((myJson) => {
            myChart.data.datasets[0].data = myJson;      
            myChart.update();
        }).catch((error) => {
            console.error(error);   
        });
    }

This code sends the dates via the variable x. This worked well with Firefox and Chrome, however with iOS Safari I had some trouble at the server end. Often the server would read the header and miss the data which followed it, but not always. To fix this problem I made use of the "Content-Length" parameter in the header: clearly this is what it is for. The relevant C for this is:

    if(strncmp(buffer, "POST /data.json", 15) == 0){       
        chstr = strstr(buffer, "Content-Length:") + 16;         

        n = atoi(chstr); // content length
        chstr = strstr(buffer, "\r\n\r\n") + 4;        

        n2 = 0;
        while((n2 += strlen(chstr)) < n){ // for iOS Safari

            m = read(sockfd, chstr + n2, 1023 - strlen(buffer)); 
            if(m < 0) error("ERROR reading from socket");  
            //printf("length read: %d, %s\n", m, chstr);     
        }
        db_read_send_JSON(sockfd, chstr);
        return;
    }


The character sequence "\r\n\r\n" signifies the end of the header. The buffer is more than large enough to contain all of the data being sent for current use, but the code as it sits is not robust against overflowing the buffer if used for something else.

The CSV download

The download is done using the plain HTML download hyperlink

<a id="down_load" href="graph.csv" download>Download Graph Data (graph.csv)</a>

but we then have the problem of sending the date range to be downloaded. This is done in JS by adding a query string to the URL:

document.getElementById("down_load").href = "graph.csv?" + x + "T00:00_" + y + "T24:00";

where the variable x and y contain the bounding dates. The first line of the resulting  HTTP header is then something like

GET /graph.csv?2018-11-24T00:00_2018-12-07T24:00 HTTP/1.1

which sends the data which is required by the server after the question mark. This does not effect the name of the downloaded file etc. on the client side.

Functionality

The resulting rendered front end, upon initial loading, is shown below:


Let us now look at the datepickers in action.
At this point the date values haven't been changed, as can be seen upon reading the "Date range setting:" directly below the blue box. To set the values the "Set date range" button needs to be pushed. Despite this a date before the 3/12/2018 can't be selected because this is the date in the first datepicker. Once the dates have been changed the "Update graph data" button can be pressed or the download hyperlink can be used. The graph that results upon doing this is shown below


The Code

https://github.com/wilstep/IOT-temperature-logger-for-the-BeagleBone-Black/tree/1.3

What's next

I have largely achieved what I set out to upon starting this project. I may pick it up again later or I may start a different project that builds on this, but for now I am done.