Raspberry Pi thermometer - online

In part 1 of this series I hooked up a waterproof DS18B20 to my Raspberry pi. For more fun, let’s show our temperature readings to the world!

  1. The raspberry pi runs a tiny Node JS script that reads the temperature from the sensors and submits it to my web server.
  2. The readings are stored in a RethinkDB
  3. database. Another Node JS script (an Express API) returns the temperature to the web site visitors as JSON.

The Pi script:

// read all available sensors and post to API
var ds18b20 = require("ds18b20");
var request = require("request");

var url = "API_HOST/api/temperature";

function save_temp(id, temp) {
  var postdata = { device_id: id, temperature: temp, date: new Date() };

  request(
    { url: url, method: "POST", json: true, body: postdata },
    function (err, res, body) {
      console.log(body);
    }
  );
}

ds18b20.sensors((err, ids) => {
  for (var i in ids) {
    var id = ids[i];
    ds18b20.temperature(id, function (err, val) {
      console.log("Temp ", id, " ", val);
      save_temp(id, val);
    });
  }
});

The server part. Slightly based on RethinkDB’s Todo example :)

// Import express
var express = require("express");
var bodyParser = require("body-parser");
var app = express();

// Load config for RethinkDB and express
var config = {
  rethinkdb: {
    host: "localhost",
    port: 28015,
    authKey: "",
    db: "thermometer",
  },
  express: {
    port: 8183,
  },
};
var r = require("rethinkdb");

app.use(bodyParser());
app.use(createConnection);

// Define main routes
app.route("/api/temperature/latest").get(getLatest);
app.route("/api/temperature").post(create);
app.route("/api/temperature/current").get(getCurrent);

// Middleware to close a connection to the database
app.use(closeConnection);

// get latest observations for all sensors
function getLatest(req, res, next) {
  r.table("observations")
    .group("device_id")
    .max("createdAt")
    .run(req._rdbConn)
    .then(function (cursor) {
      return cursor.toArray();
    })
    .then(function (result) {
      res.send(JSON.stringify(result));
    })
    .error(handleError(res))
    .finally(next);
}

// return current temperature and 24h avg/max/min
function getCurrent(req, res, next) {
  r.table("observations")
    .filter(function (doc) {
      return doc("createdAt").gt(r.now().sub(86400));
    })
    .group("device_id")
    .ungroup()
    .eqJoin("group", r.db("temperature").table("sensors"))
    .without({ right: "id" })
    .zip()
    .map(function (doc) {
      return {
        device_name: doc("device_name"),
        avg: doc("reduction")("temperature").avg().mul(10).round().div(10),
        min: doc("reduction")("temperature").min(),
        max: doc("reduction")("temperature").max(),
        cur: doc("reduction").orderBy(r.desc("createdAt")).limit(1)(
          "temperature"
        )(0),
      };
    })

    .run(req._rdbConn)
    .then(function (cursor) {
      return cursor.toArray();
    })
    .then(function (result) {
      res.setHeader("Content-Type", "application/json");
      res.json({ result: result });
    })
    .error(handleError(res))
    .finally(next);
}

/*
 * Insert
 */
function create(req, res, next) {
  var observation = {
    date: req.body.date,
    device_id: req.body.device_id,
    temperature: req.body.temperature,
  };
  observation.createdAt = r.now(); // Set the field `createdAt` to the current time
  r.table("observations")
    .insert(observation, { returnChanges: true })
    .run(req._rdbConn)
    .then(function (result) {
      if (result.inserted !== 1) {
        handleError(res, next)(new Error("Document was not inserted."));
      } else {
        res.send(JSON.stringify(result.changes[0].new_val));
      }
    })
    .error(handleError(res))
    .finally(next);
}

/*
 * Send back a 500 error
 */
function handleError(res) {
  return function (error) {
    res.send(500, { error: error.message });
  };
}

/*
 * Create a RethinkDB connection, and save it in req._rdbConn
 */
function createConnection(req, res, next) {
  r.connect(config.rethinkdb)
    .then(function (conn) {
      req._rdbConn = conn;
      next();
    })
    .error(handleError(res));
}

/*
 * Close the RethinkDB connection
 */
function closeConnection(req, res, next) {
  req._rdbConn.close();
}

/*
 * Create tables/indexes then start express
 */
r.connect(config.rethinkdb, function (err, conn) {
  if (err) {
    console.log("Could not open a connection to initialize the database");
    console.log(err.message);
    process.exit(1);
  }

  r.table("observations")
    .indexWait("createdAt")
    .run(conn)
    .then(function (err, result) {
      console.log("Table and index are available, starting express...");
      startExpress();
    })
    .error(function (err) {
      // The database/table/index was not available, create them
      r.dbCreate(config.rethinkdb.db)
        .run(conn)
        .finally(function () {
          return r.tableCreate("observations").run(conn);
        })
        .finally(function () {
          r.table("observations").indexCreate("createdAt").run(conn);
        })
        .finally(function (result) {
          r.table("observations").indexWait("createdAt").run(conn);
        })
        .then(function (result) {
          console.log("Table and index are available, starting express...");
          startExpress();
          conn.close();
        })
        .error(function (err) {
          if (err) {
            console.log(
              "Could not wait for the completion of the index `observations`"
            );
            console.log(err);
            process.exit(1);
          }
          console.log("Table and index are available, starting express...");
          startExpress();
          conn.close();
        });
    });
});

function startExpress() {
  app.listen(config.express.port);
  console.log("Listening on port " + config.express.port);
}

Making a HTTP GET request to API_HOST/api/temperature/current now hopefully returns something like this:

{
  "result": [
    {
      "avg": 1.1,
      "cur": 3.9,
      "device_name": "Outdoors",
      "max": 3.9,
      "min": -0.5
    }
  ]
}

Note! This simple example has no authentication whatsoever, anyone who knows the url of your API can insert to your database.


Add a comment