Environmental Monitoring with the Tessel 2
In the world of tomorrow, everything in our homes will be connected. The Internet of Things (IoT) is the real-world manifestation of that vision. The world of IoT includes network-connected appliances that have not, until now, had any such sophisticated capabilities.
The Nest product line, which started with intuitive smart thermostats and is expanding into other products, is one set of offerings in a growing market of so-called "smart home" offerings. Amazon's Echo, which combines several home integration aspects into a single platform, is another.
Our Own Smart Monitoring Device
There are many types of smart-home systems. Some remotely control a home device, like automatically turning lights on or off at certain times. Others optimize the behaviors of home systems, perhaps switching a home's electrical supply between solar and grid power at different times of the day or season.
In this tutorial, we'll hone in on the monitoring aspect of smart systems. We're going to build an air-conditioning monitoring device to collect information and store it in the cloud.
Our monitoring device will be able to detect the following over time:
- Whether the air conditioner is ON or OFF
- Relative humidity
- Air pressure
Data points from the sensors in the device will be uploaded to and stored in Sparkfun's Phant-powered data.sparkfun.com service.
If this is your first time experimenting with the Tessel 2, there are a few things you gotta do first! We recommend reading through our Getting Started with the Tessel 2 before diving into this project. We promise, it wont take that long.
Getting Started with the Tessel 2
October 12, 2016
Dive Deeper into the Tessel 2
The entire Johnny-Five Inventor's Kit Experiment Guide is great stuff if you're starting out with the Tessel 2.
Experiment Guide for the Johnny-Five Inventor's Kit
June 28, 2016
Getting Started with Phant(data.sparkfun.com)
If you've never worked with the data.sparkfun.com service before, the guide below will help you get started making your own stream for collecting data.
To build the monitoring device, you'll need the following parts:
Parts to Source Elsewhere
- 47uF capacitor
- Air Conditioner Extension Cord, 3' (Amazon)
Meet the Supporting Hardware
Non-Invasive Current Sensor
The non-invasive current sensor has a "jaw" that clamps around a wire. It can read the amount of current flowing through the wire without the wire itself having to be modified (ergo, non-invasive). By plugging the air conditioner into the extension cord and then using the current sensor to detect how much current is going through the extension cord, we'll be able to tell when the AC is running.
The current sensor's output is a current much lower, but linearly related to, the current it's sensing.
The non-invasive current sensor has a connector that looks like an audio jack on one end. It is a TRS (Tip-Ring-Sleeve) connector, shown on the left side of the diagram below. You may also have seen TRRS connectors (Tip-Ring-Ring-Sleeve, right side of diagram), which are commonly used for handsfree headsets. The TRRS breakout makes each of the pieces of a TRRS connector (the tip, each of the two rings and the sleeve) accessible. The TRRS breakout will allow us to easily connect to the tip and the sleeve of the current sensor's connector so we can read its values.
Let's begin by assembling the environment monitoring device!
Start by plugging the BME280 into the breadboard so that it straddles the DIP support ravine (notch) that runs vertically down the middle of the breadboard. Next, connect the BME280 to the Tessel 2 with jumper wires as shown in the wiring diagram:
|SCL||Port A, Pin 0|
|SDA||Port A, Pin 1|
Add the current sensor's voltage divider circuit.
Here's a closer look at the voltage divider circuit:
This voltage divider circuit "conditions" the output of the current sensor so that it is constrained to a range of input voltages that can be read by the Tessel 2 (0 - 3.3V). If you'd like to learn more about the technical details, you can read about the circuit it is based on, described in the article CT [Current Transformer] Sensors — Interfacing with an Arduino, OpenEnergyMonitor.org.
The final circuit should look like this:
Next, you'll need a sharp utility knife and a safe, clean cutting surface. On your surface, lay the Air Conditioner Extension Cord flat, and use the utility knife to separate one of the three joined sections of wire. Make the cut approximately 3 to 4 inches long—just long enough to put the current sensor's top "jaw" through and safely clip it together.
Create A Cloud Data Stream
The application that we're going to create will post data reported by the BME280 and Non-Invasive Current Sensor circuit from the Tessel to data.sparkfun.com (that's why this application does require a connection to the internet).
Before we get started with our own program, you'll need to create a new data stream on data.sparkfun.com so that you can send data to it from the monitoring device. You'll need to obtain the public and private keys for this data stream for use in the configuration of the air-conditioning monitor's code later.
Create a new "data stream". You'll want to fill the form in with something similar to this:
When you've completed the form, click "Save" and you'll be brought to a screen that looks like this:
Below that section and at the bottom of that page, you will see an option to send the keys to an email address—I recommend doing this before you proceed.
Create A New Project And Install Dependencies
You should already have a
j5ik directory—creating it is part of the Tessel software setup process. Use a terminal application to go to that directory, and then:
npm init -y;
npm install johnny-five tessel-io got;
This is going to change (
cd) to the parent directory of
j5ik/ and create an all new directory called
environment-monitor. Once created, it will then change into
environment-monitor/ and initialize a new project workspace with
npm init -y. The last line will install the modules
got into this project. The
got module provides "Simplified HTTP requests" and describes itself as:
A nicer interface to the built-in http module.
It supports following redirects, promises, streams, retries, automagically handling gzip/deflate and some convenience options
which is perfect for our project's needs!
There are four main files for our application, and they will be created in the root of the project (i.e. the
current.js: a module that exports a class called
Currentrepresents a Non-Invasive Current Sensor and inherits from Johnny-Five's
ac.js: a module that exports a class called
ACwhich represents an Air Conditioner.
config.js: your application-specific configuration
index.js: the application itself
Class Heirarchy Overview
The next portion of this tutorial is dedicated to writing the software that will run our environment monitor. The bulk of this work will be spent creating cleanly separable classes that will be layered together in our application:
Writing the Current Sensor Class
Current is a class that will extend Johnny-Five's
Sensor class. Open your favorite code editor, create a file called
current.js and save it in the
Exploring the Current Class
To provide a measurement of current in Amps,
data handler function needs to consider a number of individual data samples taken during a 1-second sampling cycle. Computations need to be performed on individual samples, and, at the end of each sampling cycle, a root mean squared current (rmsI) is calculated.
Current objects then emit a
measurement event and pass the
rmsI along to anything that might be listening.
rmsI is what we're after here—it represents the current going through the wire.
The first line of the
"use strict"; appears at the top of every code file for the air conditioning monitor, so I won't dive into it again:
Next, the module requires its only dependency: the
Immediately following that, a new class,
Current, is declared.
Current extends the
Sensor class provided by Johnny-Five.
Sensor provides all of the base mechanics needed to implement a specific analog input sensor.
Current will extend
Sensor and provide some things to support our current sensor.
Current's constructor (the method that is invoked when a new
Current object is created) defines a single formal parameter,
pin gets passed as an argument to the parent class' constructor (that is, the constructor on
super(...) is invoked.
Sensor takes care of setting up an analog input on the
After the call to
super(...), a long list of let variable declarations follows. Don't worry if these don't make a lot of sense yet:
All right, let's look at what some of these mean:
aref: the value of the particular board's analog reference, which is the top end of the voltage that the board expects in the analog input range. For the Tessel, we know this is +3.3V, but other boards have different
arefvalues (e.g. Arduino Uno is +5V).
this.io.arefshould contain the analog reference for whatever board Johnny-Five is running on currently.
cCount: track the number of "calibration" cycles that have passed. Calibration is set to occur over 5 1000-ms cycles—that is, the first 5 seconds of the instance's lifetime will be used for calibration.
sCount: track the number of samples collected in each 1000ms sampling cycle.
lastSampleI/sampleI: the value of the previous and present Amps (current, or I) value.
sumSqI: Used within individual sampling computations.
rmsI: the calculated root mean squared (RMS) Amps value for the sampling cycle.
calibration/ratioI: calculated calibration and Amps ratio based on turn ratio—a characteristic of the current sensor's hardware—and burden resistor—100Ω in our circuit. More technical details).
last: tracks the last time a sampling cycle completed.
false; will become
trueonce the calibration cycles are complete.
Next, we define a handler function for
"data" events. These
"data" events are defined within the super class
five.Sensor. The only operation that occurs at the "top level" of this function execution context is to take note of the exact date and time (
let now = Date.now()).
data events fire frequently, about once every 25 milliseconds (this is the default sampling frequency of Johnny-Five's
From there, the operations choose a fork in the road. There are two primary conditional paths:
The first path handles execution when a full sampling cycle—1000ms or 1 second—is complete.
rmsI is calculated for this sampling cycle, then resets some variables (
sCount). The value of last is assigned to the value of now; this will result in starting a new sampling cycle when the
data handler is next invoked.
If the instance is done calibrating (
isCalibrated), then it can emit a
"measurement" event with the value of the
rmsI for the sampling cycle. Otherwise it continues calibration.
That's the end of the fork that handles the end of a sampling cycle. The other fork—the second primary condition—occurs almost every time the
data handler is invoked, every time it's invoked and it's not the end of a sampling cycle.
This fork performs several computations on the most recent current value read from the sensor. The calculations here are ported from OpenEnergyMonitor.org's "EmonLib".
This second fork evaluates a single sensor reading. It increments the
sCount (sample count) by
1 in order to track the total number of samples collected within a single sampling cycle. It then performs some calcuations on the current value, with an eye toward ultimately being able to produce a
rmsI value for the entire sampling cycle.
Back out in the top-level scope, the very last line exports the
Current class object so that it can be used by other code modules. I'll skip mentioning this when looking at the code in other modules for the air conditioning monitor.
Open your favorite code editor, create a file called
ac.js and save it in the
AC class has only one dependency—the
Current class we just created.
Just like with the
Current class, the next step is to declare a new class called
AC, which extends the
Current class. Instances of the
AC class will forward the value of
setup.pin along to
super(...) during instantiation.
After the call to
let variable named
isActive is declared and assigned an initial value of
false. This will be used to track whether or not the air conditioner is active or not. (The air conditioner might be switched on all the time, but we only want to know when it's actually actively cooling).
Next, we register a
"measurement" event handler, which receives the
rmsI value as an argument. To determine if the air conditioner is active or not...
the handler first checks if the rounded
rmsI is less than the value of
setup.minimumI, which the application specifies as the "minimum amps flowing when the air conditioner is active". If it that condition evaluates to
true, then that means that the air conditioner is inactive, so set
false and return immediately. If that condition does not evaluate to
true, that is: the value of the rounded
rmsI is greater than or equal to the "minimum amps flowing when the air conditioner is active", then set
true. Note that
isActive is still the let variable declared outside of the event handler.
"measurement" event handler's job is to determine whether the air conditioner is active. It determines this by looking at the value of
rmsI (the root mean squared Amps of the sample), which it receives as an argument. The
setup object passed to the
AC constructor includes a
setup.minimumI defines the minimum Amps flowing when the air conditioner is active. If
rmsI is less than that value, then the air conditioner is not presently active (
isActive = false). Otherwise, we can deduce that it is (
isActive = true).
A bit about scope.
isActive is declared within the function execution context of the
constructor. So is the
"measurement" handler function, so code within it can access
isActive isn't accessible directly on
AC instances yet—for example, if you had an
AC instance called
ac, there is no
What we can do is define an accessor property, also called a "getter", to expose the value of
AC instance objects.
Note that we're only defining a "getter", not a "setter". You can check the value of
ac.isActive, but if you tried to set the value (
ac.isActive = true), you wouldn't be able to (it'd throw
TypeError: Cannot set property isActive of #<AC> which has only a getter). While this kind of protection isn't strictly necessary, it's important to me that I impart my preference for tamper-proof hardware state representations in my software.
Creating the Configuration Module
Before move onto discussing the actual application code, we have one last supporting module file to create:
This module contains a single export, an object containing properties whose values are relevant configuration for our application.
interval property defines how frequently, in milliseconds, new data is sent to data.sparkfun.com. I've specified 10 seconds, but you may change this to whatever best suits your version of the application. Take care to replace
[Phant Public Key] and
[Phant Private Key] with the values generated when you created your data stream. If you followed my advice earlier, you will already have an email containing those values.
Writing the Environment Monitor Application
Open your favorite code editor, create a file called
index.js and save it in the
Exploring the Application Code
As you've seen twice already in this tutorial, the first thing we do is require the modules that our application depends on. This time, we're requiring
tessel-io, as well as our own
config.js module files.
If you've previously read any or all of the Experiment Guide for the Johnny-Five Inventor's Kit, then the next part will look familiar. To quote from the guide itself:
Johnny-Five supports many kinds of development boards. The support for some boards is built right in to Johnny-Five, but others — including Tessels — rely on external plugins encapsulated in modules. That’s why the code requires the
npmmodule. Here, we tell Johnny-Five to use a
Tesselobject for IO when communicating with the board.
"ready" event handler is registered for the
board instance (which represents the Tessel 2 itself). The
"ready" event will be emitted when Johnny-Five and Tessel-IO have completed their respective initialization phases and the board is ready to interact with.
Before we start interacting with the hardware, there are two const declarations that get created. The value of
url will be passed directly to
got.post(...) and represents the API endpoint for posting data to your data stream, while the value of the
payload.body property will be updated with the present environment values to send to data.sparkfun.com.
Now we get to see our
AC class in action! The program instantiates a new
AC instance object and assigns it to
ac. If you look back at the
AC class constructor definition, you'll see that it accepts a
setup argument, an object. The
pin property of the
setup object will get forwarded on to
Current and then on to
Sensor to tell Johnny-Five which pin the component is connected to. The
minimumI value represents the minimum amount of current, in Amps, that the air conditioner uses when active.
Immediately following the instantiation, the program registers a
"calibrated" event with
ac. Remember that
AC inherits from
Current? That means that the
ac instance object will emit all of the events that come from its super class object as well. Very useful!
"calibrated" event will fire once near the beginning of the
ac instance's lifetime, and when it does, the program will treat that as an indication that all systems are "go".
The next step is instantiate a new
five.Multi object, specifying
Multi class is used to represent components that provide data from multiple sensors, each of which is represented by a Johnny-Five component class. In this case, an instance of
Multi for a
BME280 will itself contain instances of:
...all four of these sensors are packaged on the BME280. By making use of the
Multi class, you can wrangle all four sensors with one component object.
In the Johnny-Five Inventors Kit Guide Experiment 10: Using the BME280, you learned how to respond to
"data" events from the
Multi instance by creating a handler that forwarded values on to the browser via a WebSocket provided by socket.io.
In this example, I'd like to show you how to interact with sensors by simply waiting for them to be "ready" and then accessing data directly from the instance object's properties. The program will use the
board.loop(...) method, check if the
Multi instance (
env, which is short for "environment") is "ready"; if it is, then it will post data to our data stream:
And that's it!
Once all of this is saved in the right files and in the right directories, type—or copy and paste—the following into your terminal:
t2 run index.js
Once it's running, open your browser to https://data.sparkfun.com/streams/[Phant Public Key] and refresh it the browser as often as you'd like to see the data appear in your public stream. The stream that I created for this project is available here: https://data.sparkfun.com/streams/wp063JWw94CmL10wYw1W , which I exported to Analog.io and captured a portion that shows my own air conditioner going inactive, followed by a the temperature in the apartment rising until the air conditioner becomes active again.
When you're ready to deploy it full time, push the project into the Tessel 2's flash memory by using the command:
t2 push index.js
Resources and Going Further
You can find all the files for the Environmental Monitor at the GitHub repository.
For more Tessel fun, check out these other great SparkFun tutorials.