Arduino Indoor Outdoor Temperatures

There has been many times that I’ve left the house and noticed the difference in temperature between inside and outside my home. I thought a fun project would be to combine an Arduino reading the inside temperature and the outside temperature.

I had a couple ideas on how to do this. The first involved two Arduinos, one inside and one outside, both using TMP36 sensors to monitor the temperatures. The second was a single Arduino inside that recorded the temperature using the sensor and then using the Open Weather Map API to gain the outside temperature.

Both ideas had some pros and cons but I went for option two. It may not be as accurate as having an outside sensor but I think the data reported by the API is good enough that I could see the difference between inside and outside.

The code for this is available in a GitHub repo.

Parts used

Arduino Uno WiFi

TMP36 Sensor

Jumper wires

You’ll also need

A web and database server running PHP and MySQL. In the code used this is an internal server that does not accept requests from outside of the network. If you are going to use a server open to traffic outside of your network you will need to add extra security measures to ensure that unauthorised requests cannot add data.

Setup

Database

Create a database and then run the SQL that is in the data.sql file. This will create a table that stores the indoor and outdoor temperatures as well as a unique id and a created at date and time.

Data receive page

The first part of the script includes the Composer autoload file, the settings file and then uses Guzzle’s HTTP client that will be used to send a get request to Open Weather Map API.

require __DIR__ . '/vendor/autoload.php';

require './settings.inc';

use GuzzleHttp\Client;

There is then the basic security check for the key parameter in the query string.

if (isset($_GET['key']) && $_GET['key'] === BASIC_KEY) {

We create a connection to the database or output the error message and exit if it was not successful.

  try {
    $db = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASS);
  } catch (PDOException $e) {
    echo $e->getMessage();
    exit;
  }

The next section of code creates a new Guzzle HTTP client with the base uri set to the Open Weather Map API.

We then set the query string parameters to send and perform a GET request.

The response body is first converted to a string and then JSON decoded for easier use with PHP. This decoded response is a PHP object.

We now access the temperature returned by the API call and use this as our outdoor temperature reading.

$client = new GuzzleHttp\Client(['base_uri' => 'api.openweathermap.org/']);

  $params = [
    'query' => [
      'id' => CITY_ID,
      'appid' => API_KEY,
      'units' => 'metric',
    ],
  ];

  $request = $client->request('GET', 'data/2.5/weather', $params);

  $response = json_decode((string)$request->getBody());

  $outdoorTemperature = $response->main->temp;

The last part of the logic inserts a row into the database. To ensure that the temperature recordings are in the correct format we cast them as floats and user the number_format function to round to two decimal places.

  $data = [
    'indoor_temperature' => number_format((float)$_GET['temp'], 2),
    'outdoor_temperature' => $outdoorTemperature,
  ];

  $sql = "INSERT INTO data (indoor_temperature, outdoor_temperature) VALUES (:indoor_temperature, :outdoor_temperature)";

  $stmt= $db->prepare($sql);

  $stmt->execute($data);

After inserting the row into the database we return a 201 success response code to show that the row has been created.

  // Return a 201 created response.
  header('HTTP/1.1 201 Unauthorized');
  exit;

The last part in the else statement returns a not authorised response header if the query string does not contain the key parameter or if the key parameter is incorrect.

  header('HTTP/1.1 401 Unauthorized');
  exit;

Arduino sketch

The first portion of code includes the library for the Arduino Uno WiFi, this allows us to use the Ciao library for sending HTTP requests. We then declare some constants that will be used when sending the request. You will need to replace the SERVER_ADDRESS and KEY to match those used on your web server for receiving the data. Finally we declare our TMP36 sensor input pin to zero.

#include <UnoWiFiDevEd.h>

#define CONNECTOR "rest"
#define SERVER_ADDRESS "YOUR_SERVER_ADDRESS"
#define METHOD "GET"
#define KEY "YOUR_KEY"

int temperaturePin = 0;

Inside the setup function we initialise the Ciao library ready for use.

void setup() {
  Ciao.begin();
}

Inside our loop function we first calculate a five minute interval as this is how often we want to be taking a recording. We then set the last sampled time to zero so we get a recording on the first time in the loop.

  const unsigned long fiveMinutes = 5 * 60 * 1000UL;
  static unsigned long lastSampleTime = 0 - fiveMinutes;

Next we get the number of milliseconds that have passed and check if five minutes has passed since the last recording. If it has then we add five minutes to our last sampled time ready for the next recording.

unsigned long now = millis();
  if (now - lastSampleTime >= fiveMinutes)
  {
    lastSampleTime += fiveMinutes;

Our next few lines of code take the recording from the sensor and convert it to degrees celsius.

    // Getting the voltage temperature reading from the sensor.
    int temperatureReading = analogRead(temperaturePin);
  
    // Converting that temperature reading to voltage, for 3.3v arduino use 3.3.
    float voltage = temperatureReading * 5.0;
    voltage /= 1024.0;
  
    // Temperature in degrees celsius.
    float temperatureC = (voltage - 0.5) * 100;  // Converting from 10 mv per degree wit 500 mV offset to degrees ((voltage - 500mV) times 100).

The last part of code builds up a URI that we send to the server using the Ciao library.

    // Build the GET request URI
    String uri = "/data.php?key=";
    uri += String(KEY);
    uri += "&temp=";
    uri += String(temperatureC);

    // Send the data to the webserver.
    CiaoData data = Ciao.write(CONNECTOR, SERVER_ADDRESS, uri);

Index page and chart

The last piece of code to look at is the index.php file for the web server. This draws a line chart using the indoor and outdoor temperatures.

We first include the settings file and then make a database connection.

require './settings.inc';

// Try to connect to the database.
try {
  $db = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASS);
} catch (PDOException $e) {
  echo $e->getMessage();
  exit;
}

The SQL query gets all of the recordings in the past 24 hours and orders them oldest to newest. We then add all of these results to an array that we will use later to output as JSON.

$query = $db->query("SELECT * FROM data WHERE created_at >= now() - INTERVAL 1 DAY ORDER BY created_at ASC;");

while($row = $query->fetch( PDO::FETCH_ASSOC )){
  $recordings[] = $row;
}

In the head section we set some basic styling for the chart.

  <style>

    body {
      font-family: sans-serif;
      color: #444;
    }

    .line {
      fill: none;
      stroke-width: 3;
    }

    .line__indoor {
      stroke: #ffab00;
    }

    .line__outdoor {
      stroke: #34e823;
    }

    .axis path,
    .axis line {
      fill: none;
      stroke: #000;
      shape-rendering: crispEdges;
    }

    .axis text {
      font-size: 10px;
    }

  </style>

The body has no content, just two script tags. The first tag includes the D3 library.

<script src="https://d3js.org/d3.v5.min.js"></script>

The second script tag has the logic for creating the chart.

We first create a variable to hold the JSON data that we created earlier.

    var recordings = <?php echo json_encode($recordings); ?>;

Then we set some variables for margins, width and height and append an SVG element to the body that will contain the chart.

    // Set variables for margins, width and height.
    var margin = {top: 50, right: 50, bottom: 50, left: 50},
        width = window.innerWidth - margin.left - margin.right,
        height = window.innerHeight - margin.top - margin.bottom;

    // Create an svg element and append it to the body element.
    var svg = d3.select('body').append("svg")
        .attr("width",  width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

The next section does some setup of a time parse in the format returned from our database and then an array of all of the temperatures so we can get the min and max values.

    var timeConv = d3.timeParse("%Y-%m-%d %H:%M:%S");

    var temperatureRange = [];

    // Create an array of all the temperatures so we can get the min and max values.
    recordings.map(function(recording) {
        temperatureRange.push(recording.indoor_temperature);
        temperatureRange.push(recording.outdoor_temperature);
    });

We then create scales for our X and Y axes using time for the X axis and a linear scale for the temperatures on the Y axis.

For the yScale domain I’ve subtracted 4 from the minimum and added 4 to the maximum so the chart had a bit of breathing room above and below the lines. We then append the axis to the chart.

    var xScale = d3.scaleTime().range([0,width]);
    var yScale = d3.scaleLinear().rangeRound([height, 0]);

    xScale.domain(d3.extent(recordings, function(d){
        return timeConv(d.created_at);
    }));

    yScale.domain([parseFloat(d3.min(temperatureRange)) - 4.00, parseFloat(d3.max(temperatureRange)) + 4]);

    // Create the axis.
    var yaxis = d3.axisLeft().scale(yScale);
    var xaxis = d3.axisBottom().scale(xScale);

    svg.append("g")
        .attr("class", "axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xaxis);

    svg.append("g")
        .attr("class", "axis")
        .call(yaxis);

The last part of the code adds the two lines to the chart and gives them slightly different classes so we can colour the lines differently.

    // Create the indoor line.
    var indoorLine = d3.line()
        .x(function(d) {
            return xScale(timeConv(d.created_at));
        })
        .y(function(d) {
            return yScale(d.indoor_temperature);
        })
        .curve(d3.curveMonotoneX);

    svg.append("path")
        .data([recordings])
        .attr("class", "line line__indoor")
        .attr("d", indoorLine);

    // Create the outdoor line.
    var outdoorLine = d3.line()
        .x(function(d) {
            return xScale(timeConv(d.created_at));
        })
        .y(function(d) {
            return yScale(d.outdoor_temperature);
        })
        .curve(d3.curveMonotoneX);

    svg.append("path")
        .data([recordings])
        .attr("class", "line line__outdoor")
        .attr("d", outdoorLine);

Improving

There are a couple of things I’d like to do to improve this.

  1. Security. To allow the data to be sent to a public facing server some more security steps than a basic key should be used.
  2. Allow different timeframes on the chart, e.g. past X hours, days or weeks. For this I’d need to group the data as showing data for every 5 minutes of an hour over 7 days would clutter a chart.

My first day with an Arduino MKR GSM 1400

I have a project in mind that needs internet connectivity but I won’t have access to WiFi so when I saw the Arduino MKR GSM 1400 with it’s GSM capability I thought I could use it to send my data over 3G.

Before I get to trying anything like that this is my experience using it for the first time.

Where do I put the SIM Card

I already had a SIM Card from GiffGaff to use I just needed somewhere to put it.

Spoiler alert: it’s on the bottom.

The bottom of the Arduino MKR 1400 with the SIM card slot on the right
The bottom of the Arduino MKR 1400 with the SIM card slot on the right

I was looking at the board still stuck in the soft foam and couldn’t for the life of me find the SIM card slot until I turned it over.

First Sketch

As you’d expect there is all the information you need to know about getting started on the Arduino website.

I followed the instructions and had the classic blink sketch up and running in seconds.

Second Sketch

I found a simple sketch that uses the GSM library to send a message. But that gave me the following warning

WARNING: library {0} claims to run on {1} architecture(s) and may be incompatible with your current board which runs on {2} architecture(s).

This was caused by including GSM.h rather than MKRGSM.h

From there I went to the MKRGSM Library page which has some useful examples including one to send an SMS message.

The sketch uploaded sucesfully but it got stuck on connecting.

I swapped the new SIM card for the one I use in my phone and that one worked. So I tried the new SIM card in a phone and it worked fine. I put the new SIM back into the Arduino and tried the sketch again, and still no joy using the new SIM card.

The top SIM does not work in the MKR 1400 but the bottom one does
The top SIM does not work in the MKR 1400 but the bottom one does

Going Forward

Overall the board was quick and easy to get going with. The hardest part is going to be figuring out why the SIM card isn’t working in it. Hopefully I can sort that and then get on with utilising the board in some projects.

 

I’ve got an Arduino kit

I recently found out about Arduino an open-source electronics prototyping platform – as described on the website.

It’s basically a circuit board which can have components connected to it and then “sketches” can be uploaded to the board to control how it operates. The basics, which I am working through this Christmas, are controlling LEDs but if you have a Google or YouTube there are some really impressive projects.

I’ve never really dabbled with electronics before but this looks a lot of fun. There is a project to control your garage door with iOS. There are also numerous examples of connecting to the board over a network so I’d love to combine some web work or iOS work with Arduino.

Starter kits can be found online pretty cheap and there are plenty of good tutorials around as well.