Introduction: Radio Frequency Temperature Sensor for (Arduino OUT, Raspberry Pi IN)

About: Student NMCT at Howest Kortrijk (Belgium)

At our second home, we have a few refrigerators and freezers, but we cannot monitor their temperature from our primary residence. That is why I decided to monitor it via a web interface on a Raspberry Pi.

But then comes the next problem ... My temperature sensors are too short to go from both the Refrigerator AND the Freezers to the Raspberry Pi. Luckily, the invention of Radio Frequency and Micro controllers makes it easier for us.

This tutorial is written to guide you through all the steps, from the basics to the more advanced features of this project.

The project itself was an assignment for my school, but will eventually be used in the field.

The whole project cost a total of €160 I was unlucky with shipping costs from the USA, and I paid extra for faster shipment through some suppliers.

You can download my complete Arduino and Python code via Github. On Github, you can also find the Fritzing scheme's from the next step, and a complete Database Export.

Step 1: Materials I Used

In the first step I will explain all the components I used in this project.


RECEIVER PART

  • 1 Raspberry Pi 3 B
    • 1 Breadboard
    • 1 T-Cobbler (optional, but recommended)
      • - 1 Flat-cable for the T-cobbler (Required if chosen for T-cobbler)
    • 1 Radio Frequency Receiver (433 MHz)
    • At least 3 Male-Male Jumper cables (Female-Male if you prefer to work without the T-Cobbler)

      INCLUDED IN MOST RASPBERRY PI PACKAGES
    • 1 SD-Card (Min. 8GB)

    • 1 5V Power Supply (Micro-USB)

      NOT INCLUDED IN PACKAGE AND OPTIONAL

    • UDP Cable (You can also work via Wi-Fi, but Ethernet is recommended, I will use this in the tutorial)

    • HDMI Cable + HDMI compatible screen (Not required, I used it because I had it available)

TRANSMITTER PART

(Depending on your amount of fridges / freezers you want to monitor, you need to change the amounts, 1 set for each. I used 2 sets)

  • 2 Arduino UNO's
    • 2 Breadboards
    • 2 Radio Frequency Transmitters (433 MHz)
    • 2 DS18B20 Waterproof temperature sensors
    • 2 4.7kOhm resistors (I used 2 10kOhm resistors in parallel to equal one 4.7kOhm resistor)
    • 2 5V Arduino Power Supplies
    • At least 4 Male-Male Jumper cables (For Breadboard to Arduino)
    • A bunch of Breadboard cables
      (You can changes these to Male-Male jumper cables as well, but I prefer not to)

      INCLUDED IN ARDUINO PACKAGE
    • 2 USB cables
  • Plug boxes - Depending on the situation, you might need a plug box, or more. I use these to power on the Arduino at the same moment.


NOTE: In some countries, specific Radio Frequency modules are prohibited, please take care when buying these. Check the correct specifications.

Tools

  • Scissors to cut wires
  • Heat shrink to isolate wires
  • Heat source to warm up the heat shrink
  • Soldering Iron
  • Soldering Tin
  • Knife to punch holes & cut plastic

Step 2: Working With the Breadboards

These pictures are made with the Fritzing tool and shows the setup for both the Receiving and Transmitting end of our project.

Receiver

The Receiving part is the easiest of both. Following the data sheet of the RF transmitter, we note the following pin lay-out

GND - DO - LO - VCC VCC - GND - GND - ANT

  • GND goes to the (-)-part of the breadboard
  • VCC goes to the (+)-part of the breadboard
  • DO = Digital Output, this is where your data will be stored, connect this to the RX-pin of your Raspberry Pi
  • LO = Linear Out, this can be ignored
  • ANT = Antenna, This should be around 13 cm's of curled up wire, you can easily curl up an electrical wire around a pencil to make the curled up wire.

Transmitter

The Transmitter part is a bit more tricky, as we will also include the sensor here. The screenshot of the Fritzing breadboard scheme should contain sufficient information to build it correctly.

For the Transmitters, you do not have to use a twisted antenna, but it is recommended to make it a bit longer (30cm) for a longer range of data. If you increase the voltage level on the Transmitter, the power of the signal will also increase. But please note that our Arduino Uno cannot give more than 5V for the Digital Output pin we have put it on. If you prefer to use a voltage level higher than 5V, please try working with a Transistor and an external power supply ranging from 5V to 12V maximum.

You might wonder why we connect the VCC of the Transmitter to a Digital Output pin on the Arduino, and not just to the 5V of the Arduino, like we did with the VCC of the Receiver.

For this I will explain a little bit about Radio Frequency Transmitters.

When a Transmitter has no data to send, it will send noise. This noise is caught up by the receiver as junk data. When a Transmitter has useful data to send, it will send it out with no problem. It would not be a problem to just ignore the junk data, and wait for meaningful data to be sent through, and only work with that. The annoying part starts when we have a second Transmitter that sends out data when the other one is asleep. Note that asleep for the transmitter means that it will send out noise / junk data. One solution would be to disable the transmitter via the Digital Output pin. If you send a high signal to the Digital Output, the transmitter will go on, if you send a low signal, the transmitter will shut down.

Examples of this will follow in the Arduino code.

DS18B20

Back again to the rest of the scheme. The Waterproof Temperature Sensor DS18B20 requires a resistor of 4.7kOhm to be put in parallel over the Data and 5V wires. Since I do not have any 4.7kOhm resistors, I used 2 10kOhm resistors again in parallel, this halves the value and makes sure you still have enough. Now you can connect the yellow wire of the sensor to any of the digital Arduino pins via the use of a Jumper cable. I had to solder the wires of my sensor to a pin so that I can place it in the breadboard. It is recommended to also isolate the wires with some heat shrink if you have that available.

Repeat this whole procedure for every sensor you need. For me, one sensor means one refrigerator / freezer. When your devices are close enough to one another, you could try combining multiple sensors through one Arduino or even another microcontroller. The choice is your own.

When you are all ready with connecting the wires and components, we will now proceed to the next step which will include the coding for Arduino.

Step 3: Writing the Arduino Code

In this step I will guide you through the special parts of my code.

Basic understanding of Arduino (C) code is required. You should have already have installed Arduino for this step. If not, you can follow the guidelines on the official Arduino website where you can buy Arduino's, download the Arduino IDE and learn the basics of Arduino.

One-wire

For the Arduino code, I have used only 1 library. The one to read the digital values of the One-Wire Temperature Sensor. An Example code of the DS18x20 is available under "Edit" -> "Examples" -> "One-Wire". If you do not see this example, check if you have installed the One-Wire library. If not, you can find everything on this webpage. I have modified this code a bit to only include the parts we need. Feel free to keep the complete example if you like.


The rest of the code is not that difficult. Although, I have a few remarks to give about the choice of specific parts. For the sensor data to come through correctly, I have chosen to send the data a total of 30 times each loop. The data can be sent through Serial.print() as long as we will read the Serial in our Python script later on. Everything you print through the Serial Monitor will also be sent wirelessly over the Radio Frequency and will end up through the TX and RX pin on the Raspberry Pi.

Data package

I wrote a small function to loop n amount of times to build up a package. One package contains 3 parts The preamble, the data value and the checksum

  • The preamble is just an ID we add to identify the package content to the correct sensor.
  • I incremented the data value with 50 (degrees) to make sure it's a positive value, as in Binary, a negative value looks differently.
  • The checksum is calculated by using the XOR operator on the value. A checksum is necessary to make sure that the data came through valid.

The choice of XOR'ing the value with 11 was made by googling a bit and through the help of my teacher who told me it is a good value for the checksum. To test if your checksum is good, you can check if your results differ enough with the data value.

An example of a wrong data value and checksum would be the following package:

[Data, Data, Checksum] → [6, 3, 9]

In this example, the Checksum is calculated based on adding up the previous data values. On the receiving end, we could again add them both up, and hope to receive the same checksum.
But what if one of the values is wrong? Then the Checksum isn't the same any more [6, 3, 9] → [7, 3, 9]
And what if both the Checksum and the data come through wrong? [6, 3, 9] → [7, 3, 10]
Now, the checksum is still correct, but they both came through wrong, and the data is thus incorrect.

To avoid this, we choose an operation a bit more complex then just adding all the data values.

Bitshifting

The next special thing in my code would be the bitshifting for the data package.

unsigned long val = preamble; // The first element in our byte_sequence

val = (((val << 8) | value) << 8) | checksum; // the other elements with bitshifting and OR operator

Basically, what I did is I took the preamble and I put it in a data type of unsigned long. This allows me to add a package consisting of 3 bytes (24 bits)

As I mentioned before, the first byte contains the preamble, the second the data and the third the checksum.
In my example, I will be using the values 01010101 as the preamble (Decimal: 85). the value of temperature sensor will be 01001110 (Decimal: 78, this equals a temperature of 28, since I added 50) and a checksum of 01000101 (Decimal 69).

In binary, the leading zero's are removed, so all of these bytes would transform to 1010101 1001110 and 1000101 respectively. If we want to add these 3 in one line, we have to perform some shifting and or operations.

  1. The first step in the operation would be to shift the preamble 8 bits to the left. That way we become 0101010100000000 (For the sake of completion, I will keep the leading zeros)
  2. The second step is to add the data line to this. since the data line contains 0000000001001110 we can OR it to the previous 2 bytes. We will now become 0101010101001110.
  3. The third step is to shift this 8 bits to the left again. The result is 01010101010011100000000
  4. We only need to OR our checksum to this, and then we have the complete sequence. It's as easy as that. The OR'ing gives me a result of 010101010100111001000101.
  5. This is now our final sequence, luckily, you do not have to do this yourselves, as your Arduino microcontroller can do all this work for you with the use of the code above.

In the next steps we will decode this sequence again in Python with the necessary checks.

One last thing - Delays

Our Arduino programs might start running at the same time, if they booted together. To avoid interference in the wireless radio frequency transmission, we have to make sure they are asynchronous all the time.

I made sure that one sensor starts after 10 seconds, the other one starts after 30 seconds. If you increase the number of Arduino's you are going to use, please recalculate the delays.

Once you have uploaded your code to both Arduino's, you can continue to the next step where we will perform the setup of the Raspberry Pi.

Step 4: Raspberry Pi Part

For this part of the tutorial, basic knowledge of Linux commands is required.
I will start off with a clean install of Raspbian (NOOBS) which is free to download and use.

To access your Raspberry Pi through the command line, you will have to know your IP-Address. Depending on your router setup, you can find it there. A second option would be to make use of the APIPA address by plugging an Ethernet Cable from your Raspberry Pi to an Ethernet Port on another computer. If you have a HDMI screen and cable available, it is very easy to setup everything that way. The choice is yours.

I use PuTTY to access my Raspberry Pi through the CLI (Command Line Interface)

First of all, you will want to make a new user for your Raspberry Pi, as the default one is totally not safe. I will refer you to the documentation on the Raspberry Pi website.

Serial enabling

Once you are logged in to your new user account, you will have to perform the command

sudo raspi-config

This blue screen will popup. Use the keyboard arrows to navigate to the 5th option "Interfacing options"

Selecting the 6th option Serial will allow you to access the serial connection necessary for the data transmission.

You will have to reboot the Raspberry Pi after performing the previous commands.

sudo reboot now

That should do the trick.

MySQL database

Now, for the data storage we will have to install MySQL. This tutorial can guide you through the basics. Please remember your settings, as you will need them a few times. For my project, I created one user with only the necessary privileges, to be prepared against hackers. NEVER use your root account for the database connection on the website.

Now that we are still here, we will install Flask-Mail already. This allows us to send emails from the Flask application to an email address. The following commands should be enough to install Flask-Mail.

git clone https://github.com/mattupstate/flask-mail.git
cd flask-mail
sudo python setup.py install
sudo pip install Flask-Mail --upgrade

The next step will guide you through the decoding of the sensor values from Arduino in Python.

Step 5: Writing the Python Code on the Raspberry Pi

For this part of the tutorial, I will explain the basics of my Python script and what some parts do.
I used PyCharm for remote developing on my Raspberry Pi. There are plenty of tutorials on how to get started with that.

You can find my code on my Github repository.

The Script

The script itself is made out of 3 parts

  1. Setup
  2. Reading data
  3. Processing data

The setup parts includes necessary classes and sets some variables to use later

The things you will want to personalize are the preambles.

# These are the preambles we set in Arduino! (01010101, 00101001)
preambles = {1: 85, 2: 41}

These preambles are the same values you put on your Arduino codes. If you have changes those, change these as well.

The most important thing of the code is also in the setup:

ser = serial.Serial("/dev/serial0", 1200)

This allows our Python script to read the Serial input.

Reading the data

Reading the data is being looped almost the entire time of the script.

Basically what we do is, we read the lines from the serial input, and we check its content.
If its content is decode-able to bits, we can probably work with it. Otherwise, it is junk data and we can ignore it.

Once we have the byte sequence from the serial input, we can start decoding it. This involves some more bit shifting and bit operations, now only in 3 steps.

  1. For the preamble, we will shift our data line 16 bits to the right, so that we only have the Least Significant Byte remaining. We will do an AND with the hexadecimal value 0xFF which corresponds with a whole byte of 1 (11111111). If we AND that, we can extract it from it's original byte. It is not necessary to perform the AND operation, but for the sake of completion we will do it.
  2. For the data byte, we will do the same, but we will only shift the sequence 8 bits to the right. For this part, the AND operation is mandatory.
  3. The checksum is the easiest part, as we do not need to shift it, we just have to do the AND operation for a third time.

Next comes a sub-part of the reading part: validation.

This is not part of the code's core, as it still happens during the reading phase.
The validation performs the same checksum operation: XOR'ing the data byte with 11 and confirming that it is equal to our checksum byte. If it is equal, we can subtract the 50 degrees we added to the data byte, to make it to it's original value again.

To work with all the values later, I added every temperature to a list in the dictionary sensor_data.
We will also want to know which sensor we are currently reading data from. So performing a reverse lookup of the Key based on the Value in the dictionary Preambles will give us a 1 when the preamble equals 85 and 2 when it's 41. If none of those preambles are found, the data is invalid.

In my case, my data does not have to be that accurate, so I told my script to move to the next state, once 15 / 20 data packages are correct. You can increase or decrease this amount based on the accuracy you want. The longer the range you are transmitting data, the bigger the chance is to receive corrupted data. Interference between other devices could also decrease the chance of valid data.

Processing the data

The next stage of our script is the data processing. In this phase, we will check the list of our sensor data again, to make sure it's still enough. We will then take the average value. It should always be the same, but our data could contain some data from a previous reading (5 from the previous, 10 from the current), so we will take the average of those values. In case there should be a mistake during the reading of the temperature during the Arduino part of the code, we would also find that any higher values would be excluded now.

After the data is checked, we will send it to the database, linking the value to the sensor ID.

In this part of the code, we will also check if we have any mails to send to alert us of a temperature that's too hot. To do this, you will have to access the Flask code which we will explain in one of the next steps.
Checking whether or not the temperature is too high or not, I search for the previous condition of the sensor, before I added the value. After I added the value I check if the sensor condition is changed or not. They change dynamically through the use of a trigger in the MySQL Database, more in the next step.

After we have done all this, we will start this loop all over again, waiting for the data to be read out.

In the next step, I will guide you through my database setup, triggers, functions and procedures.

Step 6: The Database Part

For the database, I chose to work with MySQL, this is a free to use service, and is easy to implement into our Flask Python application.

I used the MySQL Workbench application on my computer to access my database. You might have to change some settings in your MySQL configuration files to be able to remotely access your database. I also made one user for myself, that is not the Root user, but it can perform a lot of actions I need. I also created the "Website" user, which only has the necessary privileges for the website.

Overview

In the attachment, you can watch a screenshot of the layout of the relations between the 5 tables. Not that the email_recipients table is the only table with no relationship to the sensors table. This is because it's mainly there for settings, and has nothing to do with the actual sensor readings.

The tables

A quick guide through my tables, what they're for and what they contain.

  • Sensors - This is the main table and allows you to add more sensors. They have a name and location to know which one you're talking about. The last temperature we have measured gets put into this as well. Our max_good temperature and alarm temperature are settings that can be changed later. They decide which condition the sensor is in. There are 3 conditions -> Good, Warning! and Alert!!. More about this later.
  • Sensorvalues - This table is the one that gets updated every time a sensor value is processed by the script in the previous step. The datetime gets automatically set to the current time with a trigger. Another trigger makes sure that the last entered value gets updated in the previous table "sensors". We don't want to worry about that any more. More about triggers later on in this step.
  • Sensorconditions - Good, Warning! and Alert!! are the three sensor conditions a sensor can have. Note the relationship is a one-on-one relation. Thus meaning that 1 sensor can only have 1 condition. Which seems logical to me. I have added a third column named "sensorconditions_bootstrap_value". This allows me to access to correct CSS class when we style the webpage for this. More later.
  • Alarms - Last but not least, we want to know when a device ( fridge / freezer ) gets too hot. So we will keep track of the alarms. We will monitor the moment the alarm was set, but also the moment the alarm stopped again. Maybe we can make some statistics later on.

Views

Since our table of Sensors contains a lot of numbers, and the date it is added, it would be nice to link it to the sensorconditions table, so that we always have a view what the numbers 1, 2 or 3 actually mean. I did this with a small JOIN statement. But since I will be accessing this data a lot, all at once, I wrote a view function to do this. That way I can always execute the view and get the data I want.

CREATE

ALGORITHM = UNDEFINED 
    DEFINER = `admin`@`%` 
    SQL SECURITY DEFINER
VIEW `sensor_overview` AS
    SELECT 
        `S`.`sensor_id` AS `sensor_id`,
        `S`.`sensor_name` AS `sensor_name`,
        `S`.`sensor_location` AS `sensor_location`,
        `S`.`sensor_max_good_temperature` AS `sensor_max_good_temperature`,
        `S`.`sensor_alarm_temperature` AS `sensor_alarm_temperature`,
        `S`.`sensor_last_temperature` AS `sensor_last_temperature`,
        `SC`.`sensorcondition_value` AS `sensorcondition_value`,
        `SC`.`sensorconditions_bootstrap_value` AS `sensorconditions_bootstrap_value`
    FROM
        (`sensors` `S`
        JOIN `sensorconditions` `SC` ON ((`S`.`sensor_condition` = `SC`.`sensorcondition_id`)))
    ORDER BY `S`.`sensor_id` DESC

You can of course customize this a lot to your own needs.

I used a view to customize a join of Alarms and Sensors as well.

NOTE: Views can be a good alternative for security reasons. You can limit the privileges of your MySQL account to only be able to view the SQL Views. This prevents any malicious user to execute any faulty SQL statements you want to avoid.

Procedures

In one of the next steps, I will guide you through my process of building the website. During the HTML part, I decided to build my own Graph line-chart. To get all these data, I wrote a procedure that gives me everything I want. During the execution of the query, you can state which sensor_id you want, and how many days you want to include in your graph.

( During my testing phase, I chose to always print the last 20 days of data, but when I reset my database and I inserted real data, I came to the conclusion that I won't have 20 days of data during my first 3 weeks. So I had to rewrite a lot of this procedure, to make it fail proof for that. )

The procedure exists of a few steps:

  1. Declaring variables
  2. Creating temporary table
  3. Selecting the dates
  4. Inserting the data into the temporary table

I will not go into much detail but I will show you one of the SQL statements as a teaser. The rest of my code can be found in the FreegoMonitor_SQL_Dump.sql on my Github page.

INSERT INTO tmp(`date`, `avg_sensor_value`) VALUES (adddate(start_date, loop_counter), freegomonitor.get_avg_temperature_by_date(sensor_id, adddate(start_date, loop_counter)));

This looks like one hell of a complex statement, but it is in fact not that difficult. In one of the previous SQL queries, I searched for the start_date we need (Last entered day minus the amount of loops), then we increment it with 1 each loop, with the adddate() function of MySQL. After that I call upon the power of the get_avg_temperature_by_date() function I wrote myself. More about that in a minute.
We loop this INSERT INTO-statement the right amount of times we asked for.

Functions

I cannot say too much about the functions, as they are not very special. On my webpage, I wanted to show some statistics of the temperature sensors. Mainly, the Average, the Maximum and the Minimum values. So I wrote a small function to give me what I wanted.

I also made sure I could search for the Average temperature on one particular date, instead of the whole period. I just added a WHERE clause to that function. I can call this function by giving the sensor_id I want + the date in the format "2017-01-01" for example.

SELECT AVG(sensorvalues_value) FROM sensorvalues WHERE sensor_id = input_sensor_id AND datetime LIKE concat(input_date, "%");

Triggers

The last thing I did in my MySQL Database was adding some Triggers. If you are not familiar with the idea of triggers, they are mainly used to automate some tasks, and perform actions AFTER or BEFORE another action.

I chose to automate the setting of the current datetime BEFORE inserting my sensorvalues data.

I also have an AFTER INSERT trigger which allows me to check the condition of my temperature sensor.

- If my newly added temperature is below the max_good_temperature the condition is set to 1 which equals Good.

- If the temperature is between the max_good_temperature and the alarm_temperature, I will set it to 2 which is Warning! in my case.

- The 3rd value is Alert!! which only comes up when the temperature is above the alarm_temperature setting.

The second part of my trigger updates the sensor_last_temperature column in the sensors table to make sure I really have the last one.

To automate my alarms, I worked with an AFTER UPDATE trigger on Sensors. Whenever a sensor condition is changed to 3, the Python code added a line to Alarms. I chose to combine the Python code with the MySQL, and the trigger on this table makes sure that an alarm stops when the condition is Good again.

That would be all there is to say about my database. An in depth view of the queries I used will be available in one of the next steps.

I will now guide you through my webpage, built with HTML, CSS, Javascript and Python with the use of Flask.

Step 7: Time for Webdesign - FLASK

In the attached screenshots of my webpage, you can see the site in action with some test data. At this very moment, I am making use of this project, but the sensors are not attached to the fridge. They are lying around my house, a few feet away from me. This also explains the high temperatures.

Flask & Jinja2

For rendering my pages, I have used the framework Flask which is Python based and allows to render HTML templates with the use of Jinja2 - a template framework. This combination allows me to use some powerful Python codes and integrate them on the webpage. The routing system Flask uses makes the use of .htaccess - for the people who might know that - unnecessary.

As I mentioned in one of the first steps of this tutorial, I have included the library Flask-mail through which I can send emails from my Python code. If you might have skipped that part, please do so now. We will need it to send our alert emails, and the contact script.

The code itself should be quite clear, once again, this can be found on my Github webpage.

Jinja2 filters

I just want to clear one thing out, that I struggled with quite a bit.

As you can see in the screenshots above, in my graph, I have written the dates in the format 'DD-MM', but in my Database, I have a datetime column in the format of "YYYY-MM-DD HH:MM:SS". I had to enable a jinja filter as following:

def datetimeformat(value, format='%d-%m'):
	return value.strftime(format)
jinja2.filters.FILTERS['datetimeformat'] = datetimeformat

This allows me to execute a Python function called datetimeformat() that formats my datetime. I can call this function like this:

value|datetimeformat

In the following step, I will quickly explain the choice of my HTML and CSS markup.

Step 8: Time for Webdesign - HTML & CSS

For my HTML and CSS styling, I chose to work with the frameworks of Bootstrap. I have customized my own Bootstrap files, as to include only the necessary parts. You can visit my customization through this link. If you prefer to just use my code, that's also fine, I have not modified any of the customized Bootstrap CSS or Javascript files, so it should be the same. But if you want to add more functionalities to the website, you should consider getting your own custom files.

HTML

A short note about my HTML:

Every screen is made out of the header, which includes the Logo I made and the navigation on the right.
In the footer, I provided space for the contact form, which only appears once you have logged in with your password.

For the moment, I haven't made the option for multiple user accounts, but I have protected the webpage with a password, so it cannot be reached by anyone. There are plans for this in the future.

I chose to work with "boxmodels". This is a term for boxes friend of mine used when they introduced me to web development a few years ago. I continued the concept. Basically, I always have a box that consists of a header, a section and optionally a footer. By giving it the class boxmodel, they stick together nicely through the use of my CSS styling. Inside these boxmodel headers, I can give a small title about what's in that box.

The boxmodels allow for easy grouping of data and I can easily modify the positions as well.

CSS

The CSS shouldn't require too much of an explanation, as you can mostly see what everything does.

The most important parts of the styling is done by Bootstrap. I only added my own boxmodels, header and navigation customization, the footer and I added some margins on the left and right of the page.

The custom CSS I wrote is available in my style.css file located under css in the static folder of my Flask project.

The most CSS is used for the graph, which requires quite a bit markup:

  • Grid lines
  • Polygons → Rectangles
  • Data circles & Data lines
  • Axes
  • Labels

Graph

I always like to use libraries to do things for me, so that I do not have to reinvent them, but some of those require quite a bit of work to implement. So one evening, I decided to check out SVG tutorials on line-graphs or scatter-diagrams. I tried it out and it worked quite OK, and extremely easy to implement. A quick tutorial how I did it.

SVG elements aren't used on a daily basis by any beginning web designer I reckon. So I will quickly comment on the SVG elements.

SVG-tagwill be your overall parent element where you will style the width and height of your graph.

G-tag is kind off like a Div element. It allows you to group elements, you can give them ID's and classes just like any other tag.

Inside of a G element you will see 4 other child elements in my graph.

  1. Polygon → A polygon is an element with a variable amount of corners. In my case, I make a rectangle, so I need 4 corners. The way this SVG element works is by providing coordinates of every corner.
  2. Line → Line elements basically draw a small line from one coordinate to another.
  3. Circle → A circle does what it says, it generates a circle. You will have to provide the coordinates of the center, and the radius of the circle.
  4. Text → Nothing as useful as good ol' text. This required no more than the coordinates of the start.
<line x1="40" x2="40" y1=10" y2="325"></line>

<polygon points="40,10 40,325 815,325 815,10"></points>

<circle cx="100" cy="100" r="3"></circle>

<text x="2" y="325"></text><br>

As I wrote just above this paragraph, you will notice the graph is divided into a few different parts. I will guide you over every one of these 5 parts real quick.

Axes

On a line-graph, you have 2 axes, the X and Y. These will be your horizontal and vertical base-line respectively. Note: You will need to put your labels underneath the X and to the left of the Y-axis, as you will want them to be read. This is why you cannot put your X and Y to the complete lefthand-corner side of the SVG element. So I decided to move them up a bit.

Polygons

The polygons I used are 3 rectangles. They will provide us with a coloured background for the chart. Bootstrap provides us with colours for Success, warning and danger. These colours represent our 3 conditions of the sensors. Thus they can also be used in the chart to show when a temperature would be in the danger zone.
The ending Y-coordinate is calculated based on the value from the database. If my alert_temperature is for example 12°C and my max_temperature value is set to 15, then that means the top 3 Y-coordinates should be coloured danger red.Since the chart starts at Y-coordinate 10, we can already add 10 to our calculation. We also know that the space between 2 lines equals 10 dots as well. So we come up with the formula:

((max_temperature - current_sensor[4]) * 10) + 10

in which current_sensor[4] equals our sensor_alert_temperature column from the sensors table, thus 12 in our example.

We will do the same for the warning part of the chart. This will start from the previously calculated danger-Y-Coordinate and go all the way to the good-Y-Coordinate.

Grid

Next element on our list is the grid. I made up a grid based on the previous calculations, and I put it in a loop, so that we can easily update the values for future releases. Vertically, I build up 20 lines for my 20 dates. The calculations start from 15 dots on the X-axis, and adds 40 dots every line.

Horizontally, I wanted to give every fifth line a more visible colour. The lines in between don't need these, it's just easier to read all the values if you have the full grid. I just manipulated the for-loop to do this. Every element that can be divided by 5 (15, 10, 5, 0, -5, -10, -15) gets a white line with opacity 0.7. All the others get a sub_line class which sets their opacity to 0.2

Labels

Our labels titles are static, they start near the beginning of their corresponding axis.
The label values (the dates (x) and the temperatures (y) ) are also calculated based on their number. A trick to "remove" all values between 2 main lines is to put their position to the far left, off the grid. I pushed them to the x-value -1000 so that they won't show up on the graph itself.

Data

Last but not least is the data. These circles have a radius of 3, their position is calculated based on their data-value. If the temperature is 12, the position is set to the corresponding y-coordinate of 12. If it's the first element, it gets put on the first vertical line on the x-axis.

I also let my graph draw a line between one dot and the next, so you have a nice line diagram.

That's basically all there is to tell about my graph. I have plans to update the graph and make it more dynamic, so that you can scroll through time, zoom in on specific periods of time, but that is for future releases.

The next step will be the last one of the front-end side, although it's actually website back-end ...

Step 9: Time for Webdesign - Using the Database in Flask

This part of the tutorial will explain you how to access your MySQL database using Flask and Python.

Mysql_controller

In order to use MySQL in Python, you have to install the controller. You can easily do this by selecting the corresponding package in PyCharm. You can also do it through the command line interface.

pip3 install mysql-controller

Depending on the version, you might have to specify an older version if yours doesn't work. They might contain errors.

DbClass.py

In my Github code, you can find a folder called model inside the Web parent folder. This directory always includes my classes I write for Python code. Inside you will find the DbClass.py script. This one allows the connection to be made to the database and provides us with the ability to perform queries.

As I mentioned earlier, it is possible to rewrite your queries so that you only execute views and procedures instead of UPDATE, DELETE, SELECT and INSERT INTO scripts, for optimized Database Security.

After you initialized your database connection, you can start writing queries.

Every query I write is for one purpose only. When it's a SELECT query, I will return a list of results back to a variable I declared in my Flask script. I can then use that result variable for different actions. (Building up the table with my sensors, gathering my graph data ...)

You can experiment with other versions of DbClasses you might find on the internet, you can even rewrite this query to optimize it's security.

Step 10: Finishing Off

To finish off the project, you could 3D Print some nice boxes to put the Arduino's in. Or create your own with different other DIY projects available on Instructables or somewhere else.

I chose not to do that, since I want to develop the project even further for a future releases, and that would require some more testing, other setups and would potentially increase the size of my box.

Since we had a few boxes at home - a bit overkill, size-wise - I decided to put it in one of those, they are easy to disassemble, and I can move it around easily. I poked a small hole inside the top plastic lid to put the Antenna through. One corner of the lid was removed to put all the cables through as well.

The same goes for your Raspberry Pi. You could use one of the available Raspberry Pi cases, which might not look as good as your Raspberry Pi, but at least it's covered.

I chose to keep my Raspberry Pi in a case that allows a flat cable to go through it, so that I can access the GPIO pins and keep the Raspberry Pi closed. But that's your choice.

Step 11: How to Use?

You might be wondering, how do I use this application?

It's not that difficult to start using it.

The first time you will be deploying this project, you will have to position your Arduino's with the temperature sensors in the correct place. Depending on the strength of the power supply on the Transmitters and the length of the Antenna, the distance between your Raspberry Pi and Arduino's.

  • Position the Arduino's so that the antenna's are facing the Raspberry Pi Receiving antenna.
  • Plug both your Arduino's and your Raspberry Pi into a power supply. Power them both at the same time if possible. Through the use of one or more plug boxes.
  • Depending whether you use Wireless or Ethernet, plug your UTP cable from a router/modem to your Raspberry Pi's Ethernet port.
  • Surf to the IP Address you have received.
    • If you do not have immediate access to the IP address of the Raspberry Pi - because you moved it after building it - search for it using a HDMI compatible screen and cable
  • If you have correctly built the database, you should have setup a few Alert Email Recipients. If you haven't done that, head over to the database and add a few into the corresponding table.
    • If you don't have access to the database, you will notice that the alarms are added to the alarms page.
  • After a few minutes you should receive your first data in the homepage of the website. If it does not appear, try moving the sensor or the Raspberry pi closer, and check the Antenna's.
  • If you receive an email telling you the temperature of one of your sensors became too hot, do not panic, if you check the source of sudden heath change, fix that and the temperature drops, you have solved the problem.

Step 12: Problems I Faced

I have encountered quite a few problems during the whole process.

When I started this project, I had no idea how my data would be transmitted from my Transmitter to my Receiver over Radio Frequency. I Googled quite a bit and talked to one of my professors telling me I should search for Manchester encoding. During a while process of trying to get the Manchester library for Arduino to work, I accidentally found out my Serial Monitor of my Arduino can be sent Wirelessly through Radio Frequency.

When I noticed that, I began sending packages of data through the Serial.print() of my Arduino.
Only then did I notice I could never send a whole bunch of data in different lines, because they could come up in another sequence, or there could be interference of another transmitter at the same time. So I decided to put it all in one line, and it worked fine for quite some time.

Then when I showed my code to another professor, he told me I could modify and optimize my code with little effort. It would require the Preamble, Data value and CRC (Cyclic Redundancy Check) to be in binary and be sent over the Serial Monitor where it can be easily decoded in Python. I managed to do this after modifying 90% of my code.

The second problem was: What if my Arduino's power up at the exact same time, after - let's say - a power shutdown? That was when I decided to add a random sleep to set both Arduino's on an asynchronous time schedule.

I already stated in one of the past steps, that my Transmitters should be turned off, in order to not cause any interference. This was also one of the problems that bugged me the most, but I found the solution which was explained in the same part of the tutorial.

Before I began this project, I already had a few things in mind I had to take care of, once I started the project. But during the whole developing phase, these things began to slip my mind. In the last week I finally thought I forgot something, and I quickly made sure it was possible.
One of these things was to make sure that I also get a notification in case my Raspberry Pi would power off. Otherwise I wouldn't get a notification that my sensor data is too hot. I found a free tool called Uptimerobot which notifies me when my website is unreachable. I have configured this to my own account, so you will have to do the same in order to get these emails.
During a demo, you will not get this, because you have to make an account


While I was testing the data with the graph, my script would crash, because my MySQL procedure only worked with at least 20 dates in the data, which didn't happen any more after I flushed the data, thus the data would fail to be requested and the script would crash.

Step 13: The End

This was all, it was so much fun working on this project, and I hope to continue and expand it later this summer.

If you have any questions regarding this project, feel free to leave a comment below or contact me.

Internet of Things Contest 2017

Participated in the
Internet of Things Contest 2017