Wednesday, March 6, 2019

Involute gear simulator built with QT and OpenGL, Part 2

Part 1

This time we will review a few key concepts which are needed to understand involute gears, and then have a look at a few things we can learn, using our simulator. The code and a 32 bit Windows compiled binary are available at my GitHub page. You will need an OpenGL compatible graphics card with drivers to run this application. Compiling the code on Linux is straightforward, it should also be possible on OSX but I haven't tried that yet.

As I described previously, an involute curve is obtained by unwinding an imaginary string from a circle (well for gears it's a circle). To proceed we need to define the terminology for the parameters that specify a given gear. There is quite a bit of redundancy in the terminology used to specify gears. Lets start with the size of the teeth, this may be specified in terms of the pitch, the diametrical pitch or the module, and each of these three parameters are related to each other by simple equations because they all obtain the same information. A given gear will have N teeth, and the pitch is the distance between neighbouring teeth for a gear with very large N. The diameter of the gear is proportional to the number of teeth and the module is the diameter per tooth while the diametrical pitch is the inverse of the module; diametrical pitch = 1 / module. Now as we are working on a simulation, we don't care about the absolute size of a gear, so long as we draw everything in proper proportion and thus we don't care about the module or the diametrical pitch or the pitch and so we will just work with module = 1, but we do care about the number of teeth N.

Now to proceed imagine we have two wheels, one driving the other using friction, and for the sake of this argument there can be no slip. Each diameter of these two wheels coincides with what is called the pitch diameter in a gear. The involute diameter used to generate the gear teeth is smaller than the pitch diameter. This is illustrated in the diagram, where the involute generating circles are also depicted and if we imagine the smaller gear A is winding the string onto its involute circle which is being unwound from the involute circle of gear B we see the essence of how the involute gear scheme works. Another important parameter is the pressure angle, as shown in the diagram. The pressure angle determines the ratio between the involute diameter and the pitch diameter, which depends upon the pressure angle but not the number of teeth.

Click to enlarge


We are now in a position to appreciate the magic of involute gears: that being why they don't depend upon the distance between them to maintain constant velocity. Constant velocity refers to the driven gear always rotating at the same speed relative to the driving gear as the teeth move in and out of mesh, as opposed to the driven gear rapidly speeding up and slowing down. The shapes of the involute teeth are solely determined from their involute generating circles and note that if we move the gears further apart these circles don't change. What does change is the pressure angle, because the angle of the string connecting the two circles obviously changes (refer to diagram). But it is clear that the rotation of the two gears determined by the string unwinding from one involute circle and onto the other doesn't change upon altering the distance between the gears. But it is this string that traces out the involute teeth and obviously it also maintains the constant velocity ratio when the separation between the gears is changed, that is how the magic works.

There is one more thing we need to cover here, and it is rather easy. A gear's outside diameter needs to be bigger than its pitch diameter for the teeth of mating gears to engage with each other. The convention with involute gears is to make this outside diameter for a gear equal to the pitch diameter of a gear which has two more teeth. That is a value of twice the module is added to the pitch diameter to obtain the outside diameter. The teeth need some clearance down the bottom, so rather than cutting them to a depth of twice the module a value of 2.157 is used. Side clearance is also needed for the teeth and the standard here is to make the tooth width 0.48 of the pitch leaving 0.52 for the gap between teeth.

So the teeth are always the same depth for a given pitch regardless of N, but the difference between the outside diameter and the involute diameter gets smaller as N is reduced. When N is too small the teeth will penetrate the involute circle. The convention to deal with this is to continue the tooth below the involute circle by using a straight radial line for the rest of the profile. Whether a real gear has this feature or not will depend on how it is manufactured. If it is made using a form cutter it will likely have the feature, but mass produced gears are more often made by a generating method such as hobbing, and then they won't. Regardless: the section of the gear tooth below the involute circle is completely useless, it can't be used for transmission of torque and is only there to provide clearance. But when is there enough clearance and when isn't there? This will depend upon what gear meshes with what and we will learn more about this soon enough.

The standard pressure angle for involute gears used to be 14.5 degrees. Despite being deprecated many decades back these gears are still around (imperial gears, not metric). These days the standard pressure angles are 20 and 25 degrees with the former being far more common. But why where things changed and what are the pros and cons? When the pressure angle is increased the degree to which the teeth slide against each other becomes more pronounced resulting in more friction, but the problems with teeth penetrating inside the involute circle are reduced. With this in mind let us look at a small gear of pressure angle 14.5 degrees running with a larger gear and see what happens

Fig. 1. (Click to enlarge) A 16 tooth gear engaged with a 80 tooth gear at
the correct depth. The gears have a pressure angle of 14.5 degrees

Fig. 2. (Click to enlarge) Same as Fig. 1. but zoomed in. Note the pair of teeth
towards the top, which have an overlap problem due to a lack of clearance
towards the root of the 16 tooth pinion's gullet.

In Fig. 1. & 2. we can see that there is a problem with teeth overlapping. This is due to a lack of clearance at the root of the pinion's (the smaller 16 tooth gear) tooth. This occurs because the simulation rotates the driven (80 tooth gear) at a constant velocity; in a real situation there would be no overlap but the constant velocity would be lost.

Fig. 3 (Click to enlarge) same as above except the pinion has been reduced to
8 teeth.


This overlap problem gets worse if we make the pinion smaller which we can see in Fig. 3 where the pinion has only 8 teeth. Obviously it gets less if we increase the pinion size and by the time we get to 20 teeth there is very little overlap left.

Fig. 4 (Click to enlarge) same as Fig. 3 except the pressure angle has been
increased from 14.5 to 25 degrees


A way to deal with the overlap problem is to increase the pressure angle. Fig. 4 is the same as Fig. 3 with the pressure angle increased to 25 degrees. The resulting overlap is drastically reduced.

Fig. 5. (Click to enlarge) Same as Fig. 3 except the gears are moved a
further 0.75 units apart.


Another way to deal with the overlap problem is to separate the gears further from each other. Doing this results in no lost engagement because a pinion tooth can only engage on its involute section anyway, and we haven't moved it far enough to loose any of the involute. In Fig. 5 this can be seen with the gears moved a further 0.75 units apart. Recall that the pitch diameter is 1 unit per tooth and the depth of tooth engagement is 2 units. Because the involute curve is invariant to the separation distance, this is a really good solution, and if you get this simulation going on your own computer you can see how well it works.


Fig. 6. (Click to enlarge) Same as Fig. 5 but using the circle approximation
for the involute curve.
A final point we will look at is the circle approximation that is often used to make form cutters to manufacture gears. This approximation works by using a circle for the involute curve, which has its gradient and curvature matched to the involute's at the point where the involute is on the pitch circle. The errors in this approximation gets worse as the number of teeth in the gear are reduced and as the pressure angle is reduced. Fig. 6 shows the extreme case where the pinion has only 8 teeth and a low pressure angle of 14.5 degrees. While the dramatic changes to the tooth profile of the pinion are easily spotted, the bigger 80 tooth gear is very different. For the 80 tooth gear it is very difficult to see any change at all. I should mention that an 8 tooth pinion is extremely small for an involute gear, and so things are really being pushed to the limit. I believe there are better circle approximations for really small gears. If I was to try and derive one, I would calculate the average of the least square difference along the involute section of the tooth flank and find the circle which minimises it. This is a standard mathematical technique. The benefit of doing this is that it would focus on finding the best circle over the entire tooth rather than just extrapolating at the pitch circle.

In the final part of this series we will look at an overview of how the some of the more interesting aspects of the code work.


Tuesday, February 19, 2019

Involute gear simulator built with QT and OpenGL, Part 1

Part 2

I have built a C++ application to provide a high resolution  rendering of a pair of involute gears running together. This post will focus on introducing what it is and what it does. An image of the application is shown below and the source code (along with 32 bit Windows binaries) is available at: https://github.com/wilstep/OpenGL-involute-gears-simulation/releases/tag/v1.3.1
This simulation allows one to slow the gears right down and examine them under great magnification, and from any direction to observe in great detail how the contacts between the gear teeth change in time and how the involute gear form actually works. In effect it lets one examine the gears rotating as if they are being viewed under a high powered microscope, but from any direction and at any chosen speed, and with perfectly sharp edges. So one can view the detailed interaction of the gear teeth with a full virtual reality visualisation.


The simulation is highly realistic and detailed with the correct involute form being calculated to all the standards used in gear specifications including the correct amount of clearance, correct tooth depths and standard pressure angles. There is also a fillet radius at the bottom of the teeth, which was surprisingly difficult to implement. Things are arranged such that one gear drives the other, and the speed at which they rotate may be adjusted. The number of teeth on each of the gears can be changed and the rendering may be zoomed in, shifted and rotated with the mouse. Further the distance separating the two gears may be tweaked and the light source for the rendering may be moved. The above image is shown below after rotating, zooming and moving the light source.


There are two common types of tooth profile systems used in gear design. The first is the cycloidal gear form that is used mainly in horology (mechanical clocks and watches) and the second is the involute form which is far more common. My simulation only deals with involute gears.

An involute is a mathematical curve which is obtained from some generating shape. In the case of gears the generating shape is a circle. Imagine we wrap a string around this circle with a pencil tied to the end, and then draw a spiral as the string unwraps. This spiral is the involute and a very short piece of this forms the profile on the gear tooth. There are several nuances that need to be appreciated to form a gear from the involute curve, which I will discuss in the next post. The special feature of the involute gear form is that the driven gear rotates at constant velocity, regardless of errors in the separation distance between the two gears.

I plan to make a further two posts subsequent to this. The next one discussing more details about the gear profiles, their calculation and also demonstrating some important insights that can be gained from this tool, and then a final one looking at some of the more interesting snippets of the code.

Part 2

Thursday, February 7, 2019

Object Orientated Design to build a sphere in OpenGL

Introduction

The motivation for this project is to do some object oriented design with C++. The problem I have chosen is to draw a sphere in OpenGL without repeating vertices. Modern OpenGL does not provide any direct means to draw a sphere, rather the user needs to render a collection of triangles to achieve this. Each triangle is specified by three vertices which coincide with its corners. So it turns out that each vertex is shared by several triangles and we are left with the problem of not repeating the same vertex.

The approach I will use provides an efficient O(n log(n)) algorithm (n being the final number of vertices or number of triangles). However the algorithm or code will be special to this type of problem where we are dividing triangles. There are more general approaches such as searching for repeated vertices and removing them, or perhaps  making the vertices first and then finding the triangles second. But with these methods you will tend to get an O(n^2) algorithm, although this is probably not such a major drawback in practice and things can be sped up with the use of a hash table or some other search mechanism. Regardless; at the end of the day we will end up with a good little API to make a sphere in OpenGL.

The Problem

The basic approach to make a sphere by dividing triangles is easy to find on the web. We will start with an octahedron which consists of eight triangles. A topological graph of an octahedron is shown in Fig. 1. An octahedron is formed from eight triangles, which neighbour each other, and we wish to divide these triangles into more triangles to get something that is much closer to a sphere.

Fig. 1. Topological graph for an octahedron.

We can divide each of these triangles into four smaller triangles as shown in Fig. 2. We then normalise the lengths of all the vertices to unity to approximate a sphere of unit radius. Thus for each triangle we make three new vertices, but these vertices are shared with neighbouring vertices, and  we don't want to repeat ourself. We are left with the task of assigning the new vertices when needed and asking neighbouring triangles for them when they already exist. To do this we will use some object oriented design, by making a sphere object which is formed from a collection of triangle objects.

Fig. 2. Diagram showing how a triangle is divided into four smaller triangles.

The Objects

The sphere

Our sphere class has the following data:

    std::vector<GLfloat> verts;
    std::vector<GLuint> inds;
    std::vector<triangle> trigs;
    const GLuint order, nVerts, nTri;
    GLuint triCnt, triCntOld, vertCnt, vertCntOld;


There a three vectors, the first of which (vert) represents the set of vertices for our sphere which specify the corners of the triangles, the second (inds) is the set of indices which specify the vertices to form each triangle (as used by OpenGL), and the third (trigs) is the set of triangles. We also have (order) the number of iterations where each triangle is divided into four new triangles. If order = 0 we simply use the octahedron and if order = 1 we divide each of the octahedron's triangles into 4 new triangles only, then stop. If order is bigger we iterate the same procedure order times. The constants nVerts and nTri are the total number of vertices and triangles we end up with, and the last line of variables are just for counting these numbers as we iteratively build our sphere.

The triangles

Our triangle class encapsulates the following data:

    std::array<GLuint, 3> index; 
    std::array<GLuint, 3> neigh_tri; 
    std::stack<std::array<GLuint, 3> > lifo;

The first two arrays of length 3 hold the indices (index)  for the triangles three vertices and the indices (neigh_tri) for the three neighbouring triangles. These indices refer to the vectors in the sphere object, and only triangles which share an edge are considered neighbours. There is also a stack (called lifo) which these two arrays can be pushed onto.

Labelling conventions for triangles and Dividing triangles

We employ the following labelling convention. When we look at the face of a triangle from outside the sphere it either points up or down as shown in Fig. 3. The vertex indices (for the triangles' array: index) are ordered according to whether they are left, right, or top/bottom as per Fig. 3. In addition the neighbouring triangles (for the triangles' array: neigh_tri) are also ordered according to the orientations given in Fig. 3. These conventions are then used to subdivide our triangles while keeping tack of all the vertices and neighbours. Keeping these conventions in mind is crucial if you wish to delve into the full details of the source code.



Fig. 3. Diagram showing how the indices are ordered for both the vertices and the neighbouring triangles, when the triangles are pointing up or down.


When we subdivide a triangle the triangle object pushes its two array objects onto its stack. This triangle object will then become the smaller triangle in the centre of the subdivided triangle as shown in Fig. 2. But first we make all the new vertices and store the corresponding indices in the triangles array (index) that they belong to. 

To do this without repeating vertices we use the size of the triangles' index in the sphere class to establish a precedence. If the index of the neighbouring triangle is less than the one we are currently working on, then this vertex has already been made and we get the required index off the neighbour. Otherwise we make a new vertex.

After doing this we make three new triangles for each existing triangle, then re-establish which triangles neighbour each other while popping the stacks at appropriate times. This is all easier said than done, but you may read the code for full details.

The Code

The C++ code is available from my GitHub page: https://github.com/wilstep/OpenGL-sphere/releases/tag/v1.0. The code is arranged so that it can easily be reused as an API if you want to draw a sphere in some other application. As it stands it is set up for use on Linux. There is a single command line argument which sets the order variable. The resulting approximate sphere is rendered with lighting and shading, and the approximate sphere rotates on its y-axis. The figure below, Fig. 4, shows the order-0 sphere which is a octahedron. However this octahedron has the normal vectors for a sphere and so the lighting is all incorrect for an octahedron.

Fig. 4. An order-0 sphere, which is actually an octahedron. The edges are hard to see (in places) because the shading normals are for a sphere, not an octahedron.


The next figure, Fig. 5, shows an order-4 sphere that looks very convincing (2,048 triangles), however if you run this on your computer and focus on the spot where the light is reflected it will still be apparent that the sphere is rotating. If we go to order-6 (32,768 triangles) then this artefact disappears.



Fig. 5. An order-4 sphere which is composed of 2,048 triangles.
 That is all for this little project.




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.



Sunday, November 25, 2018

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



click for Part 1
click for Part 2
click for Part 4

Introduction

In this post we will make our web-client display a graph of the live data being collected  by the BBB. This graph can be updated to use the latest data upon pressing a button.

Continuing on from the previous post we add the following:
  • Add a graph using Chart.js which can have its data updated using the JS fetch() function. For this the JSON data format will be used.
  • Ensure that only one instance of the server can be run in the same directory, so we don't inadvertently have multiple processes duplicating what is written to the database.
  • Have the server find the serial number of the W1 temperature sensor automatically.
  • Put the database logging back into the server, and make it such that the temperature is recorded every half hour with no option to change this interval or to turn the data recording on and off. If the server is running the data logging is on.
I have now tested the front end on  Google Chrome, Firefox and iOS Safari.

The Graph

Chart.js provides an API to make HTML graphs using JS. In our case the date will be required for the lower axis data and Chart.js makes use of Moment.js for this. The necessary  libraries are provided using CDN's in the HTML file. The Chart.js library is able to work with JSON data straightforwardly.

When the HTML is loaded our JS draw() function is called. This function asks our server for the file "data.json" using the fetch() function. Rather than write this file and then send it our server just sends what would be it's contents directly to the socket stream, the actual file "data.json" never exists. Finally our draw() function calls our drawChart() function and passes the JSON data to it. This function draws our graph.

The Chart.js documentation provides a means to alter or update the data in an existing chart. The chart is updated using our updateData() function which is called when the HTML button "Update graph data" is pushed. A picture of the resulting front end is shown directly below:

The front end rendered in Google Chrome
The front end has two buttons, the first of which is just what we had in the last post. The second button ("Update graph data") causes the graph to update when it is pressed. Every half hour another data point is added to the database, so if more data has been added since the graph was last updated it will be added.

The front end

The front end is in the files "graph.html" and "graph.js" which are provided by the server. The JS first waits for the "DOMContentLoaded" event, which occurs when the HTML has finished loading, and then calls the draw() function. The draw() function gets the JSON data from the server and then calls the drawChart() function which draws the graph on the web-page.
The "Update graph data" button on the web-page calls the updateData() function which reloads the JSON data from the server.

The Server

Single Instance

The server writes and locks a pid file to make sure it is not already running in the given directory. Another instance can be run in a different directory, which will have its own database file. This eliminates more than one instance of the server writing duplicate data to the same database file. The pid file is locked using Linux's C function flock(). When the program and all its child processes stop or are killed the lock will cease.

Finding the serial number

The probes driver directory, which depends explicitly upon its individual serial number is found using the Linux's C function scandir(). The directory we are looking for will start with "28-" with the number 28 signifying that the one-wire device is a temperature probe. If there is more than one temperature probe connected there will be additional directories starting with "28-". It has been arranged that the first probe directory found is used. If a temperature probe is not found a temperature of 100,000 degrees is returned.

Database logging

The database logging process is called very early on as a forked off child process. It logs the temperature to the database every half an hour. If the program is killed but this child process is still running the pid file lock will prevent the program from being started again in the same directory. The parent and all child processes must be killed before the program can be restarted.

The Code

https://github.com/wilstep/IoT-temperature-logger-for-the-BeagleBone-Black/releases/tag/1.2

or on the command line obtain with the command:

git clone -b '1.2' https://github.com/wilstep/IoT-temperature-logger-for-the-BeagleBone-Black.git

What's Next

I would like to add some input options to restrict the time range which the graph is plotted over, and allow the graph to be replotted upon adjusting this input. Then I'd like to add an option to download the currently plotted subset of data as a CSV file, but I haven't settled on how this will all work out or how to do it yet.

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.

Friday, November 9, 2018

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

click for Part 2
click for Part 3
click for Part 4 

Introduction

In this series I am going to make a network based temperature logger using a one wire temperature sensor (this is a digital sensor and readily available online) and a BeagleBone Black (BBB). At this early stage I plan to have fairly minimal functionality, with a server running on the BBB which periodically measures the temperature, and then access this over the internet using a client on another machine.

The server will be written in C and make use of SQLite for the database. When it is set to run it will log the temperature at a regular interval, simultaneously recording the temperature and the time of measurement. The client will probably be written to run in a web browser using HTML with ReactJS. The client will be able to set some parameters in the server, such as turning on or off the data collection (of the temperature at a regular interval) and changing the interval between measurements. The client will also be able to obtain data from the server, plot a graph of it and download it to a file.

In this entry I will concentrate on getting a fairly complete working version of the server, and use a very crude client written in C to test its functionality.

Setting up the One Wire Sensor

To begin with it is necessary to connect and configure the one wire sensor on the BBB. This is easy to do if you have a new enough version of the Linux kernel, and I strongly recommend updating if you are having trouble. I am using Debian Stretch with kernel 4.14.67, for which everything worked very smoothly. Once you have a new enough system you can follow the simple instructions on this page https://github.com/beagleboard/linux/issues/142 and you should be in good shape.

The probe has three wires, a positive, a negative, and a signal. The signal needs to be configured with a pull up resistor. The probe is nominally 5V but you need to use 3.3V on the BBB or risk frying it. Because of this lower voltage I used a 3.3k resistor instead of the recommended 4.7k. The probe's accuracy is not sensitive to this, so it doesn't matter much.

Each probe has its own serial number and this will effect the name of the directory where the driver file is located. In my case I have

/sys/bus/w1/devices/28-041750bdd9ff

where the 28- is the signature for the temperature probe and the 041750bdd9ff is particular to my individual probe. If you navigate into your version of this directory you may issue the command

cat w1_slave 

In my case this produced

40 01 4b 46 7f ff 0c 10 bc : crc=bc YES
40 01 4b 46 7f ff 0c 10 bc t=20000

where the YES signifies a successful temperature read and the 20000 gives 20000/1000 = 20 degrees Celsius.

The Server

As a starting point for the server I used the simple bare bones version given here http://www.linuxhowtos.org/C_C++/socket.htm This server makes use of the Linux fork() function to spawn off child processes, so that it can handle multiple client requests at once. The functionality I have added requires a major expansion of this code.

As it stands the function of my server depends upon the first character of the stream sent from the client when it initialises communication. When the server is started it does nothing until some instruction is provided from the client. Valid first characters are a, b, c, d & e: providing the following functions
  1.  Starts the server measuring the temperature and logging it to the database with a period that has been determined elsewhere.
  2. Stops the server from measuring, provided it is currently measuring.
  3. Reads the data from the data base
  4. Changes the period for measurement. The initial period is set to 1 minute
  5. Shuts the server program down completely
Upon starting the server it checks to see whether the database file (temperature.db) exists, if it doesn't it writes an empty initial SQLite3 database with a table called tbl1, which has two columns (time, temperature) by issuing the following SQLite command:  

create table tbl1(time varchar(23), temperature real).

Lets start the server on port 20001 by first compiling it on the BBB (you will need to change line 170 of server.c to match the directory belonging to your particular probe serial number) and then typing:

server 20001&

at the command prompt in the directory where the server program has been placed. We can then run the client program on another machine, or on the BBB itself. I ran it from a local Linux machine where the BBB can be accessed by the name beaglebone.local. So the client was run providing the following response:

client beaglebone.local 20001
Please enter the message: a
Temperature monitoring started 


So upon running the client I was prompted for a message, which I entered as simply the single character a.  The client then responds that: "Temperature monitoring started". Next we run the client again:

client beaglebone.local 20001
Please enter the message: d30
delay time set to 30


and this time we entered 'd30' which results in the server's response "delay time set to 30". After some time various entries will build up in the data base. These can be read by the client:

client beaglebone.local 20001
Please enter the message: c
8
2018-11-08T13:38, 18.8
2018-11-08T13:39, 18.8
2018-11-08T14:00, 18.9
2018-11-08T14:30, 19.3
2018-11-08T15:00, 19.5
2018-11-08T15:30, 19.8
2018-11-08T16:00, 20.0
2018-11-08T16:30, 20.1
database output done 


In this case we responded with the character c and the server then responded with the database entries recorded so far. The first line from the server gives us the integer 8 which is the number of measurements returned. When the measurements were started the default value for the period of 1 minute was set, then some 3 plus minutes later this was changed to 30 minutes and the measuring continued. This was done in the afternoon when the temperature of the room I was in was increasing. Note that the server automatically synchronises the measurements to coincide with the turn of the hour. If an integer value which is not a factor of 60 (7 for example) is chosen for the period, then the first measurement of the next hour will be shifted forward to fall on the turn.

The Code


If you wish to obtain the code used in this project you can download if from

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

Keep in mind that the server needs to be compiled on the BBB and that the client needs to be compiled where it will be used. This could be on the same BBB or another Linux computer. It might compile on OSX using gcc but I haven't tried it.

What's Next?


In the next instalment I will focus on improving the client to provide a GUI interface which plots a graph. The server will probably be tweaked a bit too; for now I would like to make it detect the temperature probe's serial number automatically, add an enumeration type for the response to the client's input and perhaps reference the time periods to the turn of the day rather than the turn of the hour.

Go to part 2