“How To: Smart Home setup to track indoor temperatures and humidity with sensors, Raspberry Pi, MQTT, Node.js, Vue.js and Chart.js”
At first, setup your sensors whereever you like in your appartment and connect your RTL-SDR receiver with the Raspberry Pi. How to receive the raw data out of the nearby area? A little open source software will help us with that: rtl_433 is a program (Github link) to decode traffic from devices that are broadcasting on 433.9 MHz. We install it on our Raspberry Pi:
# prepare os
sudo apt-get update
sudo apt-get upgrade
# install dependencies
sudo apt-get install libtool libusb-1.0.0-dev librtlsdr-dev rtl-sdr cmake
# clone rtl_433 Git Repository
git clone https://github.com/merbanan/rtl_433.git
# change to directory
cd rtl_433/
# make build folder
mkdir build
cd build/
# compile
cmake ../
# make
make
# install
sudo make install
# test if rtl_433 was properly installed
rtl_433 -h
After that we test if the program detects our RTL-SDR receiver with
rtl_433 -G
I had some trouble with the following error: _Kernel driver is active, or device is claimed by second instance of librtlsdr. __In the first case, please either detach or blacklist the kernel module _(dvb_usb_rtl28xxu), or enable automatic detaching at compile time.
The repository owner recommends
sudo rmmod dvb_usb_rtl28xxu rtl2832
in that case. If everything went well, the receiver is ready to get sensor data and rtl_433 helps with processing, so that after a few seconds you should get signals in the nearby area (yes, even temperature sensor data of your neighbor’s or sensor data of connected cars.)
Hurray, hardware is running!
“How To: Smart Home setup to track indoor temperatures and humidity with sensors, Raspberry Pi, MQTT, Node.js, Vue.js and Chart.js”
The main problem in this part is how to setup a stable and endlessly running task on a Raspberry Pi to continuesly receive the (correct!) sensor data. Secondly we want it to somehow process and transfer it to a service that works as our middleware to provide the frontend with JSON data to display. MQTT is the IoT protocol to go with but how can a MQTT broker like Mosquitto work with a Node.js backend properly?
Multiple solutions would be possible here, but I decided for a Javascript Node.js backend which will be run with Express.js. Make sure node and npm are ready on your machine or install them first.
node -v
npm -v
Setup a project folder and a make a subfolder “server”. Create a new file “server.js” within and code a basic backend service. We will upgrade its functionality later.
const express = require('express');
const app = express();
// test
app.get('/', function (req, res) {
res.send('hello world!');
});
app.listen(3000, () => console.log('App listening on port ', 3000));
Don’t forget to install the npm package express in your console and start the application!
npm init
npm install express --save
node server.js
Go to your browser and check if your web server works on http://localhost:3000.
Why MQTT? The protocol is a lightweight publish/subscribe messaging transport solution which is very popular in the IoT field. We will use a MQTT broker as the control center to receive raw sensor data from our rtl_433 program, we installed in the previous chapter and forward them to our web server. Mosquitto is a common MQTT broker and is installed and tested on or Raspberry Pi with
sudo apt install -y mosquitto mosquitto-clients
mosquitto -v
The broker will be accessable to clients on mqtt://localhost:1883.
The Raspberry Pi now gets the important task to not only start rtl_433 to decode traffic from devices that are broadcasting on 433.9 MHz manually, but to start this task on every reboot automatically. For that, we create a cronjob with the tool crontab, which should be installed on our system.
crontab -h
On your Raspberry Pi create a new file “tsh_raspberry_script.sh” (or whatever you want) in your pi home folder and make it executable from your terminal:
chmod +x tsh_raspberry_script.sh
After that, open the file in a text editor and add the following bash script. It will start rtl_433 and pipe the output in JSON format to Mosquitto, where it will be published in the topic “home/rtl_344”. Don’t forget to close and safe the file.
#!bin/bash
/usr/local/bin/rtl_433 -F json -M utc | mosquitto_pub -t home/rtl_433 -l
Now we can set up a new cronjob which will execute the shell script on every Raspberry Pi reboot. Open up a terminal:
# edit crontabs of user "pi"
crontab -e
# a text editor will open and load all existing cronjobs, add
@reboot sleep 60 && sh /home/pi/tsh_raspberry_script.sh
In development mode a very basic database will suffice our requirements, that’s why I used lowdb (Github link) to store the sensor data on localhost first. Lowdb is based on a JSON file in our project folder.
npm install lowdb --save
In your server.js add some code. Set some defaults first, which are required if your JSON file (mine is “db.json”) is empty at first.
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync')
const adapter = new FileSync('db.json');
const db = low(adapter);
db.defaults({ posts: [] })
.write()
That’s all. Now we can write into, edit and delete data within our database.
We go back to our Node.js application and install the MQTT client MQTT.js (Github link) to be able to consume data that is available via Mosquitto.
npm install mqtt --save
With the newly installed MQTT client we are able to receive all the messages that the MQTT broker delivers over its API on mqtt://localhost:1883. We now filter them to only process and store “correct” data sets (remember: our RTL-SDR receiver found signals from multiple IoT gagdets we are not interested in).
My setup included some buffer, temperature and date parsing, basic verifying and filtering regarding incoming messages before I stored the correct Javascript Objects into the lowdb. Continue working on your server.js:
const mqtt = require('mqtt');
const client = mqtt.connect(mqtt://localhost:1883);
fahrenheitToCelsius = (fahrenheit) => {
var fTempVal = parseFloat(fahrenheit);
var cTempVal = (fTempVal - 32) * (5 / 9);
return (Math.round(cTempVal * 100) / 100);
}
client.on('message', function (topic, message) {
// message is buffer
var stringBuf = message && message.toString('utf-8')
try {
var json = JSON.parse(stringBuf);
// console.log(json);
if (json.model === 'inFactory sensor') {
if (json.id === 91 || json.id === 32) {
// catch my specific sensor model
if (json.temperature_F && json.humidity) {
// add data to lowdb
const time = moment.utc(json.time).tz("Europe/Berlin");
const formattedTime = time.format('YYYY-MM-DD HH:mm:ss');
console.log('write post');
db.get('posts')
.push({ id: uuid.v1(), room: json.id, temp: fahrenheitToCelsius(json.temperature_F), humidity: json.humidity, time: formattedTime }).write()
}
}
}
} catch (e) {
console.error(stringBuf);
}
})
That’s it. Whenever the MQTT client receives sensor data it will store it in our database accordingly. You can check that in your “db.json” file in your project folder, which grows bigger and bigger during runtime. It won’t delete itself on backend restart!
{
"posts": [
{
"id": "c107fc70-1f33-11e9-9b95-fbfea27c8046",
"room": 32,
"temp": 22.89,
"humidity": 30,
"time": "2019-01-23 18:24:34"
},
{
"id": "6607f9f0-1f34-11e9-9b95-fbfea27c8046",
"room": 32,
"temp": 22.89,
"humidity": 30,
"time": "2019-01-23 18:29:11"
},
{
"id": "16492190-1f35-11e9-9b95-fbfea27c8046",
"room": 91,
"temp": 22.72,
"humidity": 35,
"time": "2019-01-23 18:34:07"
}
]
}
Now that we have clean data in our lowdb we might want to provide them via a REST API to be consumable for a frontend or multiple frontends (smartphone app, web app, …). We already deployed a local web server running on Node.js and Express.js and can very simply add an endpoint the provides the database data with the following code. Add it to your server.js!
app.get('/api', (req, res) => {
res.send(db.get('posts'));
});
Yes, that’s it. Check if it is working on http://localhost:3000/api or with your favourite REST client (e.g. Postman).
“How To: Smart Home setup to track indoor temperatures and humidity with sensors, Raspberry Pi, MQTT, Node.js, Vue.js and Chart.js”
In this part we will build a basic dashboard displaying smart home sensor data with charts. We could go with whatever implementation you would like here: from vanilla HTML/CSS/JS to every framework which is suitable to our needs – which are basically doing an API call to our backend to fetch sensor data and lift them up to work nicely with a chart visualisation.
There are also other good solutions to deal with IoT frontends like Pimatic, OpenHAB and FHEM, but let’s just build this part completely on our own. We will go with the SPA framework Vue.js with Quasar on top: It comes with UI components in the popular Material Design, axios and some other features, that help getting started with Vue.js very fast.
Install the Vue.js (Link) and Quasar CLI (Github link) first!
npm install -g vue-cli
npm install -g quasar-cli
Go to your project root and init a new sub project for your frontend code with
quasar init vue-frontend
Now start you development server on http://localhost:8080/ with
quasar dev
and you are ready to develop your frontend according to your likes. Quasar comes with a basic starter layout including a side navigation. However we don’t need to work on this heavily and focus on the the homepage, where we will build the dashboard functionality.
Go to Index.vue and update the basic dashboard layout. You can use static images like in my proposal and put them into the src/assets folder.
<template>
<q-page padding class="docs-input row justify-center">
<div class="row gutter-sm">
<div class="col-6">
<q-card inline>
<q-card-media style="max-height: 250px">
<img src="~assets/temperature.jpg">
<q-card-title slot="overlay">
Temperature
<span slot="subtitle">hot or not</span>
</q-card-title>
</q-card-media>
<q-card-main>lorem ipsum</q-card-main>
</q-card>
</div>
<div class="col-6">
<q-card inline>
<q-card-media style="max-height: 250px">
<img src="~assets/humidity.jpg">
<q-card-title slot="overlay">
Humidity
<span slot="subtitle">some like it wet</span>
</q-card-title>
</q-card-media>
<q-card-main>lorem ipsum</q-card-main>
</q-card>
</div>
</div>
</q-page>
</template>
<style>
</style>
<script>
export default {
name: "PageIndex"
};
</script>
To fetch necessary data on page load we make use of Vue.js’ created() function in the section below the template. We insert a new function this.fetchData(); there and implement this new function in the methods’ block.
methods: {
fetchData() {
this.loaded = false;
this.$axios
.get("http://localhost:3000/api")
.then(response => {
return response.data;
})
.then(response => {
console.log(response);
this.loaded = true;
})
.catch(e => {
console.log(e);
this.$q.notify({
color: "negative",
position: "top",
message: "Could not fetch.",
icon: "report_problem"
});
});
}
}
We simply use the axios library (Github link) which Quasar included right from the setup, so we can use it globally without importing it explicitly. If this works properly you should be able to log the array of sensor data in your browser’s developer tools. If there is any problem, we trigger the Quasar component notification and include the error message.
Hint: If you encounter CORS problems in the communication between front- and backend (e. g. “No Access-Control-Allow-Origin header is present on the requested resource.”) edit your server.js and restart it as follows:
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
In this part I will focus on displaying only the temperature chart to make the tutorial more comprehensable. At first we will install the npm package vue-chartjs (Github link) which works as a practical Vue.js wrapper for Chart.js (Link).
npm install vue-chartsjs --save
Now we build a generic, reusable line chart component to use in the web application. Create a new file “LineChart.js” in the src/components folder and import the vue-chartsjs package. Furthermore, we will follow the basic tutorial for the package and specify a data collection prop and an options prop that will prettify our line chart later.
import { Line } from 'vue-chartjs';
export default {
extends: Line,
props: {
datacollection: {
type: Object,
default: null
},
options: {
type: Object,
default: null
}
},
mounted() {
this.renderChart(this.datacollection, this.options, {responsive: true})
}
}
Switch to the Index.vue again and include a new line chart component (html tag) in your template. We want to display it within the card component. Additionally we specify some properties: v-if=”loaded” will tell the component that it should only mount, if the according data prop is true. Also, we transfer the fetched datacollection_humidity and options_humidity as our generic datacollection and options into the line chart.
<q-card-main>
<line-chart
v-if="loaded"
:datacollection="datacollection_humidity"
:options="options_humidity"
></line-chart>
</q-card-main>
We also have to edit our fetchData(); function and transfer the fetched data as a processible JSON to the data collection prop.
// process the backend response and add labels and some styling for the Chart.js api
const datacollection_humidity = {
labels: response.map(obj => obj.time),
datasets: [
{
label: "Humidity",
backgroundColor: "#000",
data: response.map(obj => obj.humidity)
}
]
};
this.datacollection_humidity = datacollection_humidity;
// set some optional properties regarding axes and ticks
this.options_humidity = {
scales: {
xAxes: [
{
type: "time",
distribution: "linear"
}
],
yAxes: [
{
scaleLabel: {
display: true
},
ticks: {
callback: function(value, index, values) {
return value + "%";
}
}
}
]
}
};
Don’t forget to add the LineChart component and the necessary “loaded” data prop in your Vue.js section.
export default {
name: "PageIndex",
components: {
LineChart
},
data() {
return {
loaded: false,
};
}
}
That’s it! On every page reload, the web application fetches available sensor data from our backend service and will display it as a line chart. You would like to add more functionality? You can find all features we built and more (filtering, reloading, deploying, saving persistently, …) in my Github repository for this project.
I hope this tutorial will surely help and you if you liked this tutorial, please consider sharing it with others. Always warm in your smart home …
Thank you for reading!
#node-js #vue-js #javascript #json #web-development