The quest to wear the right clothing based on the difference between indoor and outdoor heat indexes when making my morning run is made easier using this Activewear Suggestion station. That is built using a Particle Boron with a grove shield and Open Weather Map “Current Weather Data” API.
I attached a DHT 11 sensor to collect indoor temperature/relative humidity and an OLED screen to display indoor and outdoor temperature/relative humidity collected from Open Weather Map. Finally, an RGB LED that changes color based on the difference between the indoor and outdoor heat indexes calculated using our Indoor heat index and outdoor heat index functions.
For this project I will be using Particle Workbench that provides all the tools, libraries and extensions you need for IoT development in a single, easy-to-install package.
Additionally, by clicking on this link you’ll be taken to the Particle Web IDE that is an Integrated Development Environment; which just so happens to run in your web browser. This will contain all of the code preloaded for you. All you’ll need to do is to duplicate the project.
Thanks to the ease of use of the Grove system, this should be a straightforward and accessible project even if you're just starting out.
Below is a quick video walkthrough about the Active Wear Suggestion Station:
Goal:The goal of this project is to collect and compare Indoor and outdoor heat indexes. A Heat index combines air temperature and relative humidity as "It's not the heat, it's the humidity" that matters. This allows me to make a quick decision in the morning if I should wear additional activewear before going on my morning run.
Highlights from the codeWhile it's easy to copy the code and call the project complete, it's worth looking at some of the program logic parts to figure out what's going on.
Step 1:
The first step of this project is to add the Boron on the Grove FeatherWing and connect all sensors. Working with Grove requires no soldering. Just plug in the sensors and you can focus on coding and integrations!
Below is a chart of the sensor connections on the Grove FeatherWing:
Connections:
Grove DHT 11 --> A0
Grove RGB LED --> UART
Grove OLED --> I2C_1
For detailed information, Check schematics at the bottom of the page ↓
Step 2:
After connecting the sensors successfully, now we will Pre-setup the library's includes and variable definitions.
At the beginning of the code, you will see #include
which is used to include outside libraries in your workbench. This gives you access to a large group of standard C libraries (groups of pre-made functions), and also libraries written especially for Arduino.Similarly, you will see #define
which is a useful C++ component that allows you to give a name to a constant value before the program is compiled.
These declarations will specify the libraries used in this “Activewear Suggestion Station” project based on the pin configuration that is used for each sensor.
#include <Adafruit_DHT.h>
#include <Grove_ChainableLED.h>
#include <Grove_OLED_128x64.h>
#include <Ubidots.h>
// Defining Temp Humd pin
#define DHTPIN A0
// Defining DHT11
#define DHTTYPE DHT11
// Defining Ubidots webhook name
const char *WEBHOOK_NAME = "Ubidots";
// Ubidots constant
Ubidots ubidots("webhook", UBI_PARTICLE);
// Temp Humi object
DHT dht(DHTPIN, DHTTYPE);
// LED object with respective pins
ChainableLED leds (RX, TX, 1);
void updateDisplay (double inside, double outside);
// global outdoor temp variable
float tempOutdoor = -100;
// global outdoor humditiy variable
float humidityOutdoor = -1;
// Read Humidity Data
float humidity = dht.getHumidity();
// Read Temp Data
float temp = dht.getTempFarenheit();
Step 3:
After setting up the #include
and #define
declarations, it's time to initialize and set the initial values in the setup()
section which is used to initialize our libraries and configure objects with settings that you’ll use later on in your code.
void setup() {
Serial.begin(9600);
Serial.println("Active Wear Station");
// initialize DHT library
dht.begin();
// initialize LED library
leds.init();
Wire.begin();
// initialize display library
SeeedOled.init();
// Clearing display
SeeedOled.clearDisplay();
SeeedOled.setNormalDisplay();
SeeedOled.setPageMode();
// Adding Active Wear Station at setup
SeeedOled.setTextXY(2, 0);
SeeedOled.putString("Active");
SeeedOled.setTextXY(3, 0);
SeeedOled.putString("Wear");
SeeedOled.setTextXY(4, 0);
SeeedOled.putString("Suggestion");
SeeedOled.setTextXY(5, 0);
SeeedOled.putString("Station");
Finally, the last part of the setup()
is Particle.subscribe
; Subscribe allows the cloud to send a message to many devices. In this case it is our setCurrentWeather webhook that is sending outdoor temperature and humidity from Open Weather Map API events to our Boron. More information can be found here.
// Subscribing to GetWeatherForecast webhook
Particle.subscribe(System.deviceID() + "/GetWeatherForecast/", setCurrentWeather, MY_DEVICES);
}
Step 4:
Understanding the main code loop():
After creating a setup()
function, which initializes and sets the initial values, the loop()
function does precisely what its name suggests, and loops consecutively, allowing your program to change and respond. This is used to actively control your Particle device.
For this project we are collecting 2 sources of data:
1-Measuring the indoor temperature and humidity
The indoor data collected for this project comes from the temperature and humidity sensor attached to A0
, that are defined at the beginning of the code as:
float temp = dht.getTempFarenheit();
float humidity = dht.getHumidity();
The first thing to do is to verify that the sensor data valid. Should there be any invalid data, the code will use the Serial.println()
to output, “Failed to read from DHT11 sensor!”.
// Check if any indoor data reads have failed
if (isnan(temp) || isnan(humidity)){
Serial.println("Failed to read from DHT sensor");
return;
}
2- Collecting Outdoor temperature and Humidity data
Up next is creating publishing period variables and checking the last time since last publish has occurred in order to use our API calls effectively from Open Weather Maps.
The millis()
time function returns the number of milliseconds passed since the Particle device began running the current program and subtracts it with LastPublish
. This allows us to determine if Last publish >= publishPeriod
is greater than or equal 15 minutes to publish again.
If this is true, the If statement will assign lastPublish = millis();
and publishes the weather forecast data into the Particle console using Particle.publish
.
// Publishing every 15 min
const unsigned long publishPeriod = 15 * 60 * 1000;
// Last publish variable
static unsigned long lastPublish = 10000 - publishPeriod;
// Check time since last publish occurred and publish collected Webhook Outdoor weather forecast data
if (millis() - lastPublish >= publishPeriod) {
lastPublish = millis();
Particle.publish("GetWeatherForecast", PRIVATE);
}
Particle.publish
; allows you to publish an event through the Particle Device Cloud that will be forwarded to all registered listeners such as callbacks, subscribed streams of Server-Sent Events, and other devices listening via Particle.subscribe
. More information can be found here.
LED light Logic:
If indoor heat index "inside" is lower than outdoor heat index then light turns blue meaning: NO extra activewear needed.
If indoor heat index "outside" is greater than outdoor heat index then light turns red meaning: EXTRA activewear needed.
// LED light logic based on indoor vs outdoor heat indexes
// Indoor heat index lower than outdoor heat index turn blue
if (inside < outside){
leds.setColorRGB(0,0,0,255);
}
// Indoor heat index greater than outdoor heat index turn red
if (inside > outside){
leds.setColorRGB(0,255,0,0);
}
Following the previous step, we update the OLED display when we have more data, To display the heat indexes on the Activewear Suggestion Station locally.
// Updating OLED Display
updateDisplay (inside, outside);
Finally, the last pieces of our loop()
is to add data that will be published to Ubidots:
// Ubidots Variables publish into Ubidots dashboard
ubidots.add("Indoor Temp", temp);
ubidots.add("Indoor Humidity", humidity);
ubidots.add("Outdoor Temp", tempOutdoor);
ubidots.add("Outdoor Humidity", humidityOutdoor);
ubidots.add("Indoor Heat Index",indoorHeatIndex(temp, humidity));
ubidots.add("Outdoor Heat Index", outdoorHeatIndex(tempOutdoor, humidityOutdoor));
bool bufferSent = false;
bufferSent = ubidots.send(WEBHOOK_NAME, PUBLIC);
and the Particle console:
// Particle publish temperatures, humidities and heat indexes into console
Particle.publish("Indoor Temp",String (temp));
Particle.publish("Indoor Humidity", String (humidity));
Particle.publish("Outdoor Temp",String (tempOutdoor));
Particle.publish("Outdoor Humidity", String (humidity));
Particle.publish("Indoor Heat Index",String(indoorHeatIndex(temp, humidity)));
Particle.publish("Outdoor Heat Index",String(outdoorHeatIndex(tempOutdoor, humidityOutdoor)));
Highlights from our Webhooks:Webhooks are a simple and flexible way to send data from your Particle devices to other apps and services around the Internet.
Webhooks bridge the gap between the physical and the digital world, helping you get your data where you need it to be. More information here.
In this project we use two webhook integrations:
1- The first webhook is our GetWeatherForecast: this pulls data from openweathermap.org and gives us access to current weather data by city name.
In this section we collect outdoor temperature and outdoor humidity.
{
"event": "GetWeatherForecast",
"responseTopic": "{{PARTICLE_DEVICE_ID}}/{{PARTICLE_EVENT_NAME}}",
"url": "https://api.openweathermap.org/data/2.5/weather",
"requestType": "GET",
"noDefaults": true,
"rejectUnauthorized": true,
"responseTemplate": "{\"temp\":{{{main.temp}}},\"hum\":{{{main.humidity}}}}",
"query": {
"q": "Boston",
"units": "imperial",
"appid": "Your Open Weather Map API KEY"
}
}
After creating GetWeatherForecast webhook and start collecting data, we use this JSON parser "setCurrentWeather" function for collected data as all the data collected is in JSON format and can not be used in the logic and display.
// Json Parser for data collected from Open Weather API
void setCurrentWeather(const char *event, const char *data) {
Log.info("subscriptionHandler %s", data);
JSONValue outerObj = JSONValue::parseCopy(data);
JSONObjectIterator iter(outerObj);
while (iter.next()) {
if (iter.name() == "temp") {
tempOutdoor = iter.value().toDouble();
}
if (iter.name() == "hum") {
humidityOutdoor = iter.value().toDouble();
}
}}
2- The second webhook is Ubidots: this is where we Visualize data collected by our Particle device with Ubidots graphs, charts and tables.
To learn more how to integrate your device here is a guide to Connect your Particle device to Ubidots using Particle Webhooks.
{
"event": "Ubidots",
"url": "https://industrial.api.ubidots.com/api/v1.6/devices/{{{PARTICLE_DEVICE_ID}}}",
"requestType": "POST",
"noDefaults": false,
"rejectUnauthorized": true,
"headers": {
"X-Auth-Token": "YOUR_UBIDOTS_TOKEN_HERE",
"Content-Type": "application/json"
},
"body": "{{{PARTICLE_EVENT_VALUE}}}"
}
Finally :After collecting both indoor temperature/humidity and outdoor temperature/humidity. We create two functions to calculate the indoor heat index and the outdoor heat index:Below is the indoor heat index equation:
// Indoor heat index equation
double indoorHeatIndex (float temp, float humidity) {
const double c1 = -42.379;
const double c2 = 2.04901523;
const double c3 = 10.14333127;
const double c4 = -.22475541;
const double c5 = -0.00683783;
const double c6 = -0.05481717;
const double c7 = 0.00122874;
const double c8 = 0.00085282;
const double c9 = -0.00000199;
double heatIndex = c1 + (c2 * temp) +
(c3 * humidity) +
(c4 * temp*humidity) +
(c5 * (temp*temp)) +
(c6 * (humidity * humidity)) +
(c7 * (temp * temp) * humidity) +
(c8 * temp * (humidity * humidity)) +
(c9 * (temp * temp) * (humidity * humidity));
return heatIndex;
}
Below is the outdoor heat index equation:
// Outdoor heat index equation
double outdoorHeatIndex (float tempOutdoor, float humidityOutdoor) {
const double c1 = -42.379;
const double c2 = 2.04901523;
const double c3 = 10.14333127;
const double c4 = -.22475541;
const double c5 = -0.00683783;
const double c6 = -0.05481717;
const double c7 = 0.00122874;
const double c8 = 0.00085282;
const double c9 = -0.00000199;
double outHeatIndex = c1 + (c2 * tempOutdoor) +
(c3 * humidityOutdoor) +
(c4 * tempOutdoor*humidityOutdoor) +
(c5 * (tempOutdoor*tempOutdoor)) +
(c6 * (humidityOutdoor * humidityOutdoor)) +
(c7 * (tempOutdoor * tempOutdoor) * humidityOutdoor) +
(c8 * tempOutdoor * (humidityOutdoor * humidityOutdoor)) +
(c9 * (tempOutdoor * tempOutdoor) * (humidityOutdoor * humidityOutdoor));
return outHeatIndex;
}
Provided you’ve followed the instructions correctly, you should now have a working Activewear suggestion station with a local display.
Thanks to the Particle.publish
you can also see indoor; temperature, humidity and heat index. Along with this you will be able to see outdoor; temperature, humidity and heat index remotely by going to the Particle Console and selecting the device on the my devices tab or by checking the Events tab (where you'll be able to see publishes from all your devices at once).
How to find use of Activewear Suggestion Station:1- Ubidots Dashboard is a useful tool for visualization; you are able to create a free account to build, develop, test, and learn with Ubidots STEM.
I have created an Attire Classification dashboard to help me with my morning decisions with metrics updating in real-time showing data related to indoor & outdoor heat indexes.
The main purpose of this dashboard is to provide a comprehensive snapshot of difference in heat indexes and what type of attire I should wear for my morning run.
2- Using the local display + RGB LED on station itself would help to:
- Determine what heat index is higher based on RGB Light logic:
Blue --> good to go
Red --> Wear additional active wear
- A visual display of indoor and outdoor heat indexes displayed in Fahrenheit
** KEEP IN MIND: the station does not highlight precipitation levels so please dress accordingly to stay safe and warm.
Future Work- For next steps, I will be creating a Part 2 for this project that will include: a Google Firebase webhook for using Firebase's realtime database to start storing data in real time.
Comments