Wednesday, November 14, 2018

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

click for Part 1
click for Part 3
click for Part 4 

Introduction

In this post we will narrow things down to a simple project, which will form an important step towards our overall goal. We will make a web app that communicates with a stripped down C server on the BBB. This app will be able to request the temperature and display it, nothing more. The client will be coded using HTML and JavaScript, allowing the client side to be completely handled by a web-browser, regardless of which operating system is being used for the client and making it a true IoT device. I have only tested the front end on Firefox and Google Chrome.

HTTP headers

When a web-browser makes a request to a server it sends and expects headers. We need to make our server deal with this. It is easiest if our web-page comes from the same server address as our data does; then we don't need to worry about cross-origin resource sharing in our server's header.

A basic example of an HTTP header is:

HTTP/1.1 200 OK
Date: Mon 12 Nov 2017 05:23:02 GMT
Server: C_BBB

Content-Length: 1332
Content-type: text/html; charset=utf-8


Note that a blank line must follow this header. For us the most important lines in the header are the first one and the Content-Length. The first line tells the client that the server is happy and to expect its request to follow the header. If the server can't find the request then it should send "HTTP/1.1 404 Not Found", which I'm sure we've all experienced. The length gives the number of bytes we are going to send in the message which follows.

We'll use this header to send our web-page to the client (our web-browser). Our web-page is in the file "web_client.html" which is placed in the same directory as our server. When the server is ready to call the write() function to reply to the client the following C function is called which does all the work in sending the header and the html file

void file_serv(int sock, char fn[])
{
    char buffer[512];
    int n, c;
    FILE *ifp;
   
    printf("Hi from file_serv %s\n", fn);
    ifp = fopen(fn, "r");
    fseek(ifp, 0L, SEEK_END);
    // get the size of the file in bytes, less the end of file character
    n = ftell(ifp) - 1;
    rewind(ifp);
    // Simple HTTP headers
    sprintf(buffer, "HTTP/1.1 200 OK\nServer: C_BBB\nContent-Length: %d", n);
    n = strlen(buffer);
    sprintf(buffer + n, "\nContent-Type: text/html; charset=utf-8\n\n");
    n = write(sock, buffer, strlen(buffer)); // send header to client
    if(n < 0) error("ERROR writing to socket");
    while(1){ // loop which sends the html file to the client
        n = 0;
        while((c = getc(ifp)) != EOF){
            buffer[n++] = c;
            if(n == 511) break;
        }
        if(n == 0) break; // end of file reached
        buffer[n] = '\0';
        n = write(sock, buffer, strlen(buffer));
        if (n < 0) error("ERROR writing to socket");
        if(n == 0) break; // The browser ain't buying what we're selling
    }
    fclose(ifp);
    printf("Bye from file_serv\n");
}


In this case we haven't put the date in the header, but it's not an issue because we won't be using this feature. This function is quite versatile, it will load any html file passed as an argument to it provided that it is compatible with the header. We will also use it to load the "favicon.ico" file, which is just a blank icon I downloaded and is used to keep the browser happy.

It is straightforward to make our server print the headers sent to it. When Firefox contacts our server, using the url localhost, it makes the request

GET / HTTP/1.1
Host: localhost:20006
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1


This is followed up with  the "favicon.ico" icon request with the GET line now "GET /favicon.ico HTTP/1.1" in the header.

If we append ?tt onto the URL (e.g. http://localhost:20006/?tt) then the GET line becomes: "GET /?tt HTTP/1.1". So this is what we will use to send a request to our server from the client side.

The client

The client code is small enough to reproduce here

<!DOCTYPE html>
<html>
<body>
  
<h1>Temerature Reader</h1>
<p id="mydata"></p>
<button onclick="displayResult()">Update</button>
  
<script>
    const url=document.location.origin + '?data';
   
    displayResult();
   
    function displayResult(){
        fetch(url).then(function(response){
            if(response.ok){
                response.text().then(function(myText){
                    str = myText;
                    document.getElementById("mydata").innerHTML = myText;
                });
            }else{
                console.log('Network request failed with response ' +
                         response.status + ': ' + response.statusText);
            }
        });    
    }

</script>

</body>
</html>

This code uses the JS fetch() function to contact the server whenever the "Update" button is pushed. A picture of the working client is shown directly below:


When the Update button is pressed the server is contacted, and the text line containing the time and temperature is returned and displayed as shown.

The Code 


The code can be found at https://github.com/wilstep/IOT-temperature-logger-for-the-BeagleBone-Black/tree/1.1 The relevant code is located in the subdirectory "web-trial". Compile on the BBB by issuing the command "make" and then run with the command

wserver x

where x is the port number (20000 is not a bad choice). Once the server is running you can access it from a browser. In my case when running on port 20000 the URL "http://beaglebone.local:20009/" is all that's needed.

What's next

In the next instalment we will continue with the client and the server by adding functionality. We will aim to display a graph of the temperature on the client.

No comments:

Post a Comment